1 /*
2  * Copyright 2009 Alexander Curtis <alex@logicmill.com>
3  * This file is part of GEDmill - A family history website creator
4  *
5  * GEDmill 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, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * GEDmill is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with GEDmill.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 using System;
20 using System.Collections.Generic;
21 using System.Drawing;
22 using System.Drawing.Imaging;
23 using System.IO;
24 using System.Text;
25 using GDModel;
26 using GEDmill.Model;
27 using GKCore.Logging;
28 
29 namespace GEDmill.HTML
30 {
31     /// <summary>
32     /// Base class providing general functionality required by all classes that create HTML pages.
33     /// </summary>
34     public abstract class Creator
35     {
36         private static readonly ILogger fLogger = LogManager.GetLogger(GMConfig.LOG_FILE, GMConfig.LOG_LEVEL, typeof(Creator).Name);
37 
38         protected const string PageDescription = "GEDmill GEDCOM to HTML family history website";
39 
40         // The raw data that we are turning into a website.
41         protected GDMTree fTree;
42 
43         // Pointer to the window showing the progress bar, so that web page creation progress can be shown to user.
44         private IProgressCallback fProgressWindow;
45 
46         // The same multimedia file may be referenced multiple times.
47         // This hash prevents it being copied to the output directory more than once.
48         private static Dictionary<string, FilenameAndSize> fCopiedFiles = new Dictionary<string, FilenameAndSize>();
49 
50         // The sFilename for the Valid XHTML sticker image.
51         private string fW3CFile;
52 
53 
Creator(GDMTree tree, IProgressCallback progress, string w3cFile)54         protected Creator(GDMTree tree, IProgressCallback progress, string w3cFile)
55         {
56             fTree = tree;
57             fProgressWindow = progress;
58             fW3CFile = w3cFile;
59         }
60 
61         // This clears the static list of all multimedia files copied to the output directory (and possibly renamed).
ClearCopiedFilesList()62         public static void ClearCopiedFilesList()
63         {
64             fCopiedFiles.Clear();
65         }
66 
67         // Converts all HTML characters into their escaped versions
68         // Set hardSpace to false if you want to keep first space as breakable, true if you want all nbsp.
69         // TODO: Surely there is a .Net function to do this (WebUtility.HtmlEncode)?
70         // TODO: Might want to preserve <a> links in the HTML in case user has specified them in their data.
EscapeHTML(string original, bool hardSpace)71         public static string EscapeHTML(string original, bool hardSpace)
72         {
73             if (string.IsNullOrEmpty(original)) {
74                 return string.Empty;
75             }
76 
77             fLogger.WriteInfo(string.Format("EscapeHTML({0})", original));
78 
79             int tabSpaces = GMConfig.Instance.TabSpaces;
80             var sb = new StringBuilder(original.Length);
81             int tabPos = 0;
82             bool doneCRLF = false;
83             bool doneSpace = false;
84             int length = original.Length;
85             int n = 0;
86             foreach (char c in original) {
87                 switch (c) {
88                     case (char)0x91:
89                     case (char)0x92:
90                         sb.Append("'");
91                         doneCRLF = false;
92                         doneSpace = false;
93                         tabPos++;
94                         break;
95                     case (char)0x93:
96                     case (char)0x94:
97                         sb.Append("\"");
98                         doneCRLF = false;
99                         doneSpace = false;
100                         tabPos++;
101                         break;
102                     case '<':
103                         sb.Append("&lt;");
104                         doneCRLF = false;
105                         doneSpace = false;
106                         tabPos++;
107                         break;
108                     case '>':
109                         sb.Append("&gt;");
110                         doneCRLF = false;
111                         doneSpace = false;
112                         tabPos++;
113                         break;
114                     case '\"':
115                         sb.Append("&quot;");
116                         doneCRLF = false;
117                         doneSpace = false;
118                         tabPos++;
119                         break;
120                     case '&':
121                         sb.Append("&amp;");
122                         doneCRLF = false;
123                         doneSpace = false;
124                         tabPos++;
125                         break;
126                     case ' ':
127                         if (doneSpace || hardSpace) {
128                             sb.Append("&nbsp;");
129                         } else {
130                             sb.Append(' ');
131                             doneSpace = true;
132                         }
133                         doneCRLF = false;
134                         tabPos++;
135                         break;
136                     case '\n':
137                         if (!doneCRLF) {
138                             sb.Append("<br />");
139                         }
140                         doneCRLF = false; // To allow multiple CRLFs to produce multiple <BR />s
141                         doneSpace = false;
142                         tabPos = 0;
143                         break;
144                     case '\r':
145                         if (!doneCRLF) {
146                             sb.Append("<br />");
147                             doneCRLF = true;
148                         }
149                         doneSpace = false;
150                         tabPos = 0;
151                         break;
152                     case '\t':
153                         do {
154                             sb.Append("&nbsp;");
155                             tabPos++;
156                         }
157                         while ((tabPos % tabSpaces) != 0);
158                         doneSpace = true;
159                         break;
160 
161                     default:
162                         sb.Append(c);
163                         doneCRLF = false;
164                         doneSpace = false;
165                         tabPos++;
166                         break;
167                 }
168                 ++n;
169             }
170             return sb.ToString();
171         }
172 
173         // Converts all Javascript characters into their escaped versions
EscapeJavascript(string original)174         protected static string EscapeJavascript(string original)
175         {
176             if (original == null) {
177                 return "";
178             }
179 
180             var sb = new StringBuilder(original.Length);
181             foreach (char c in original) {
182                 switch (c) {
183                     case '\'':
184                         sb.Append("\\'");
185                         break;
186                     default:
187                         sb.Append(c);
188                         break;
189                 }
190             }
191             return sb.ToString();
192         }
193 
194         // Converts all invalid sFilename characters into underscores
EscapeFilename(string original)195         protected static string EscapeFilename(string original)
196         {
197             fLogger.WriteInfo(string.Format("EscapeFilename({0})", original));
198 
199             if (original == null) {
200                 return "_";
201             }
202 
203             const string validChars = " 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz$%'`-@{}~!#()&_^";
204 
205             var sb = new StringBuilder(original.Length);
206             foreach (char c in original) {
207                 char cc = c;
208                 if (validChars.IndexOf(c) < 0) {
209                     cc = '_';
210                 }
211                 sb.Append(cc);
212             }
213             return sb.ToString();
214         }
215 
216         // Returns a string with all email addresses replaced by value of sReplacement.
ObfuscateEmail(string text)217         protected static string ObfuscateEmail(string text)
218         {
219             if (text == null) {
220                 return null;
221             }
222             int nLength = text.Length;
223             var sb = new StringBuilder(nLength);
224             int i = 0;
225             int nNameStart = -1;
226             int nState = 0;
227             const string invalidNameChars = ",\"�$^&*()+=]}[{':;,<>?/\\|`�#~";
228             const string replacement = "<email address>";
229 
230             while (i < nLength) {
231                 char c = text[i];
232 
233                 switch (nState) {
234                     case 0:
235                         // Not seen anything special.
236                         if (!char.IsWhiteSpace(c) && c != '@' && c != '.' && invalidNameChars.IndexOf(c) < 0) {
237                             // Possible name char, remember where name starts.
238                             nState = 1;
239                             nNameStart = i;
240                         } else {
241                             // Can't be an email name. Add it verbatim.
242                             sb.Append(c);
243                             nState = 0;
244                         }
245                         break;
246                     case 1:
247                         // Seen one or more name chars.
248                         if (c == '@') {
249                             // Now looking for domain.
250                             nState = 2;
251                         } else if (!char.IsWhiteSpace(c) && invalidNameChars.IndexOf(c) < 0) {
252                             // Continue looking through a possible name string.
253                         } else {
254                             // Can't be an email address. Add what we've got so far and return
255                             // to hunting email addresses.
256                             sb.Append(text.Substring(nNameStart, i - nNameStart));
257                             sb.Append(c);
258                             nState = 0;
259                         }
260                         break;
261                     case 2:
262                         // Seen at sign, now looking for domain
263                         if (!char.IsWhiteSpace(c) && c != '@' && c != '.' && invalidNameChars.IndexOf(c) < 0) {
264                             // Possible domain char.
265                             // Now looking for dot among domain chars.
266                             nState = 3;
267                         } else {
268                             // Can't be an email address. Add what we've got so far and return
269                             // to hunting email addresses.
270                             sb.Append(text.Substring(nNameStart, i - nNameStart));
271                             sb.Append(c);
272                             nState = 0;
273                         }
274                         break;
275                     case 3:
276                         // Looking for first dot among domain chars
277                         if (c == '.') {
278                             // Now looking for another domain.
279                             nState = 4;
280                         } else if (!char.IsWhiteSpace(c) && c != '@' && invalidNameChars.IndexOf(c) < 0) {
281                             // A possible domain char, keep looking for dot.
282                         } else {
283                             // Can't be an email address. Add what we've got so far and return
284                             // to hunting email addresses.
285                             sb.Append(text.Substring(nNameStart, i - nNameStart));
286                             sb.Append(c);
287                             nState = 0;
288                         }
289                         break;
290                     case 4:
291                         // Looking for valid domain char to start next domain portion.
292                         if (!char.IsWhiteSpace(c) && c != '@' && c != '.' && invalidNameChars.IndexOf(c) < 0) {
293                             // A valid domain char. Look for another dot , or end.
294                             nState = 5;
295                         } else {
296                             // Can't be an email address. Add what we've got so far and return
297                             // to hunting email addresses.
298                             sb.Append(text.Substring(nNameStart, i - nNameStart));
299                             sb.Append(c);
300                             nState = 0;
301                         }
302                         break;
303                     case 5:
304                         // Looking for a dot or end of domain among valid domain chars
305                         if (c == '.') {
306                             // Read rest of domain part.
307                             nState = 6;
308                         } else if (!char.IsWhiteSpace(c) && c != '@' && invalidNameChars.IndexOf(c) < 0) {
309                             // Valid domain name. Keep looking for dot or end.
310                         } else if (c != '@') {
311                             // Found complete email address
312                             sb.Append(replacement);
313                             sb.Append(c);
314                             nState = 0;
315                         } else {
316                             // Can't be an email address. Add what we've got so far and return
317                             // to hunting email addresses.
318                             sb.Append(text.Substring(nNameStart, i - nNameStart));
319                             sb.Append(c);
320                             nState = 0;
321                         }
322                         break;
323                     case 6:
324                         // Looking for valid domain char to start next domain portion, or can end here if address is (name@add.add.)
325                         if (!char.IsWhiteSpace(c) && c != '@' && c != '.' && invalidNameChars.IndexOf(c) < 0) {
326                             // A valid domain char. Look for another dot , or end.
327                             nState = 5;
328                         } else {
329                             // Found complete email address (ending in a full-stop).
330                             sb.Append(replacement);
331                             sb.Append('.');
332                             sb.Append(c);
333                             nState = 0;
334                         }
335                         break;
336                 }
337                 ++i;
338             }
339 
340             // Add anything remaining in email addr buffer.
341             if (nState == 5 || nState == 6) {
342                 // Found complete email address.
343                 sb.Append(replacement);
344                 if (nState == 6) {
345                     // We ended on a dot.
346                     sb.Append('.');
347                 }
348             } else if (nState > 0) {
349                 sb.Append(text.Substring(nNameStart, i - nNameStart));
350             }
351             return sb.ToString();
352         }
353 
354         // Generates navbar at top of page, in header div
OutputPageHeader(HTMLFile f, string previousChildLink, string nextChildLink, bool includeIndexLink)355         protected static void OutputPageHeader(HTMLFile f, string previousChildLink, string nextChildLink, bool includeIndexLink)
356         {
357             if (GMConfig.Instance.IncludeNavbar) {
358                 string frontPageLink = "";
359                 if (GMConfig.Instance.FrontPageFilename != "") {
360                     frontPageLink += string.Concat("<a href=\"", GMConfig.Instance.FrontPageFilename, ".", GMConfig.Instance.HtmlExtension, "\">front page</a>");
361                 }
362                 string mainSiteLink = "";
363                 if (GMConfig.Instance.MainWebsiteLink != "") {
364                     mainSiteLink += string.Concat("<a href=\"", GMConfig.Instance.MainWebsiteLink, "\">main site</a>");
365                 }
366 
367                 bool includeNavbar = previousChildLink != ""
368                   || nextChildLink != ""
369                   || includeIndexLink
370                   || frontPageLink != ""
371                   || mainSiteLink != "";
372 
373                 if (includeNavbar) {
374                     f.WriteLine("    <div id=\"header\">");
375                     f.WriteLine("      <ul>");
376 
377                     if (previousChildLink != "") {
378                         f.WriteLine("<li>{0}</li>", previousChildLink);
379                     }
380 
381                     if (nextChildLink != "") {
382                         f.WriteLine("<li>{0}</li>", nextChildLink);
383                     }
384 
385                     if (includeIndexLink) {
386                         f.WriteLine(string.Concat("<li><a href=\"individuals1.", GMConfig.Instance.HtmlExtension, "\">index</a></li>"));
387                     }
388 
389                     if (frontPageLink != "") {
390                         f.WriteLine("<li>{0}</li>", frontPageLink);
391                     }
392 
393                     if (mainSiteLink != "") {
394                         f.WriteLine("<li>{0}</li>", mainSiteLink);
395                     }
396 
397                     f.WriteLine("      </ul>");
398                     f.WriteLine("    </div> <!-- header -->");
399                     f.WriteLine("");
400                 }
401             }
402         }
403 
404         // Copies a file from the user's source directory to the website output directory, renaming and resizing as appropriate.
405         // Returns the sFilename of the copy.
406         // sArea is sAsid sub-part of image
407         // sArea is changed to reflect new image size
408         // sArea can be {0,0,0,0} meaning use whole image
409         // stats can be null if we don't care about keeping count of the multimedia files.
CopyMultimedia(string fullFilename, string newFilename, int maxWidth, int maxHeight, ref Rectangle rectArea, Stats stats)410         public static string CopyMultimedia(string fullFilename, string newFilename, int maxWidth, int maxHeight,
411                                             ref Rectangle rectArea, Stats stats)
412         {
413             fLogger.WriteInfo(string.Format("CopyMultimedia( {0}, {1}, {2} )", fullFilename, maxWidth, maxHeight));
414 
415             if (!File.Exists(fullFilename)) {
416                 return "";
417             }
418 
419             string result = fullFilename;
420 
421             if (newFilename == "") {
422                 newFilename = Path.GetFileName(fullFilename);
423             }
424 
425             try {
426                 string asidFilename;
427                 if (rectArea.Width == 0) {
428                     asidFilename = fullFilename;
429                 } else {
430                     asidFilename = string.Concat(fullFilename, ".", rectArea.X.ToString(), ",", rectArea.Y.ToString(), ",", rectArea.Width.ToString(), ",", rectArea.Height.ToString());
431                 }
432 
433                 if (maxWidth != 0 && maxHeight != 0) {
434                     asidFilename = string.Concat(asidFilename, "(", maxWidth.ToString(), "x", maxHeight.ToString(), ")");
435                 }
436 
437                 if (fullFilename != null && GMConfig.Instance.OutputFolder != null && GMConfig.Instance.OutputFolder != "") {
438                     // Have we already copied the sFilename?
439                     if (fCopiedFiles.ContainsKey(asidFilename)) {
440                         var filenameAndSize = fCopiedFiles[asidFilename];
441                         result = filenameAndSize.FileName;
442                         rectArea.Width = filenameAndSize.Width;
443                         rectArea.Height = filenameAndSize.Height;
444                     } else {
445                         // Copy file into output directory
446                         if (GMConfig.Instance.CopyMultimedia) {
447                             string imageFolder = GMConfig.Instance.ImageFolder;
448                             string outputFolder = GMConfig.Instance.OutputFolder;
449 
450                             if (imageFolder != "") {
451                                 imageFolder = imageFolder + '\\';
452                             }
453                             if (outputFolder != "") {
454                                 outputFolder = outputFolder + '\\';
455                             }
456 
457                             string copyFilename = string.Concat(imageFolder, newFilename);
458                             string absImageFolder = string.Concat(outputFolder, imageFolder);
459                             string absCopyFilename = string.Concat(absImageFolder, newFilename);
460 
461                             // If image folder doesn't exist, create it
462                             if (!File.Exists(absImageFolder) && !Directory.Exists(absImageFolder)) // TODO: this returns false if it exists but you don't have permission!
463                             {
464                                 Directory.CreateDirectory(absImageFolder); // TODO: catch failure to create, e.g. output folder not there yet
465                             }
466 
467                             // If new sFilename already exists, append a number and keep trying
468                             uint uCopy = 0;
469                             string filePart = Path.GetFileNameWithoutExtension(copyFilename);
470                             string extnPart = Path.GetExtension(copyFilename);
471                             while (File.Exists(absCopyFilename)) {
472                                 const string sAdditionalLetters = "abcdefghijklmnopqrstuvwxyz";
473                                 if (GMConfig.Instance.RenameMultimedia == false) {
474                                     uint nCopyPlus = uCopy + 2;
475                                     copyFilename = string.Concat(imageFolder, filePart, "-", nCopyPlus.ToString(), extnPart);
476                                 } else if (uCopy >= sAdditionalLetters.Length) {
477                                     // Once all the extra letters have been used up, put number as "-n", where n starts from 2.
478                                     uint nCopyMinus = uCopy - (uint)(sAdditionalLetters.Length - 2);
479                                     copyFilename = string.Concat(imageFolder, filePart, "-", nCopyMinus.ToString(), extnPart);
480                                 } else {
481                                     copyFilename = string.Concat(imageFolder, filePart, sAdditionalLetters[(int)uCopy], extnPart);
482                                 }
483                                 uCopy++;
484 
485                                 absCopyFilename = string.Concat(outputFolder, copyFilename);
486                             }
487 
488                             fLogger.WriteInfo(string.Format("Copying \"{0}\" to \"{1}\"", fullFilename, absCopyFilename));
489 
490                             File.Copy(fullFilename, absCopyFilename, true);
491 
492                             File.SetAttributes(fullFilename, FileAttributes.Normal); // Make any Read-Only files read-write.
493                             if (maxWidth != 0 && maxHeight != 0) {
494                                 // It must be a picture file
495                                 copyFilename = ConvertAndCropImage(outputFolder, copyFilename, ref rectArea, maxWidth, maxHeight);
496                             }
497                             fCopiedFiles[asidFilename] = new FilenameAndSize(copyFilename, rectArea.Width, rectArea.Height);
498                             result = copyFilename;
499                         } else {
500                             if (GMConfig.Instance.RelativiseMultimedia) {
501                                 // TODO: make path of sFilename relative to MainForm.s_config.m_outputFolder
502                                 string sRelativeFilename = fullFilename;
503                                 result = sRelativeFilename;
504                             }
505                         }
506                         if (stats != null) {
507                             stats.MultimediaFiles++;
508                         }
509                     }
510                 }
511                 result = result.Replace('\\', '/');
512             } catch (IOException e) {
513                 fLogger.WriteError("Caught IO Exception : ", e);
514                 result = "";
515             } catch (ArgumentException e) {
516                 fLogger.WriteError("Caught Argument Exception : ", e);
517                 result = "";
518             } catch (HTMLException e) {
519                 fLogger.WriteError("Caught HTML Exception : ", e);
520                 result = "";
521             } catch (Exception e) {
522                 fLogger.WriteError("Caught generic exception : ", e);
523                 result = "";
524             }
525 
526             return result;
527         }
528 
529         // Outputs the HTML to display the W3C Valid XHTML image on the page.
OutputValiditySticker(HTMLFile f)530         protected void OutputValiditySticker(HTMLFile f)
531         {
532             f.WriteLine("<p class=\"plain\">");
533             f.WriteLine("<a href=\"http://validator.w3.org/check?uri=referer\"><img");
534             f.WriteLine("src=\"" + fW3CFile + "\"");
535             f.WriteLine("style=\"margin-top:4px\"");
536             f.WriteLine("alt=\"Valid XHTML 1.0 Strict\" height=\"31\" width=\"88\" /></a>");
537             f.WriteLine("</p>");
538         }
539 
540         // Creates link HTML for the individual e.g. <a href="indiI1.html">Fred Bloggs</a>
MakeLink(GDMIndividualRecord ir)541         protected static string MakeLink(GDMIndividualRecord ir)
542         {
543             string name = ir.GetPrimaryFullName();
544             string dummy = "";
545             if (name == "") {
546                 name = GMConfig.Instance.UnknownName;
547             } else if (!GMHelper.GetVisibility(ir) && !GMConfig.Instance.UseWithheldNames) {
548                 name = GMConfig.Instance.ConcealedName;
549             } else {
550                 name = GMHelper.CapitaliseName(name, ref dummy, ref dummy);
551             }
552             return MakeLink(ir, name);
553         }
554 
MakeNote(string noteStr)555         protected static string MakeNote(string noteStr)
556         {
557             if (!string.IsNullOrEmpty(noteStr)) {
558                 return string.Concat("<p class=\"eventNote\">", EscapeHTML(noteStr, false), "</p>");
559             }
560             return string.Empty;
561         }
562 
563         // Creates link HTML for the individual e.g. <a href="indiI1.html">Next Child</a>. Uses name provided by caller.
MakeLink(GDMIndividualRecord ir, string name)564         protected static string MakeLink(GDMIndividualRecord ir, string name)
565         {
566             string link;
567             if (!GMHelper.GetVisibility(ir)) {
568                 // TODO: Why are we linking to invisible people?
569                 link = EscapeHTML(name, true);
570             } else {
571                 link = string.Concat("<a href=\"", GetIndividualHTMLFilename(ir), "\">", EscapeHTML(name, false), "</a>");
572             }
573             return link;
574         }
575 
576         // Returns a string to use as a sFilename for this individual's HTML page.
577         // The string is just the sFilename, not a fully qualified path.
GetIndividualHTMLFilename(GDMIndividualRecord ir)578         protected static string GetIndividualHTMLFilename(GDMIndividualRecord ir)
579         {
580             string relativeFilename = string.Concat("indi", ir.XRef, ".", GMConfig.Instance.HtmlExtension);
581             if (GMConfig.Instance.UserRecFilename && ir.HasUserReferences) {
582                 GDMUserReference urn = ir.UserReferences[0];
583                 string filenameUserRef = EscapeFilename(urn.StringValue);
584                 if (filenameUserRef.Length > 0) {
585                     relativeFilename = string.Concat("indi", filenameUserRef, ".", GMConfig.Instance.HtmlExtension);
586                 }
587             }
588             return relativeFilename;
589         }
590 
591         // Crops the specified image file to the given size. Also converts non-standard formats to standard ones.
592         // Returns sFilename in case extension has changed.
593         // sArea is changed to reflect new image size
ConvertAndCropImage(string folder, string fileName, ref Rectangle rectArea, int maxWidth, int maxHeight)594         private static string ConvertAndCropImage(string folder, string fileName, ref Rectangle rectArea, int maxWidth, int maxHeight)
595         {
596             fLogger.WriteInfo(string.Format("ConvertAndCropImage( {0}, {1} )", folder != null ? folder : "null", fileName != null ? fileName : "null"));
597 
598             string absFilename = string.Concat(folder, fileName);
599 
600             Image image = null;
601             try {
602                 image = Image.FromFile(absFilename);
603             } catch (OutOfMemoryException) {
604                 // Image is not a GDI compatible format
605                 image = null;
606             }
607 
608             if (image == null) {
609                 throw (new HTMLException("Unknown image format for file " + absFilename)); // Let caller sort it out.
610             }
611 
612             Rectangle rectNewArea;
613             if (rectArea.Width <= 0 || rectArea.Height <= 0) {
614                 SizeF s = image.PhysicalDimension;
615                 if (s.Width <= maxWidth && s.Height <= maxHeight) {
616                     maxWidth = (int)s.Width;
617                     maxHeight = (int)s.Height;
618                     // Nothing needs to be done, bitmap already correct size.
619                     // Carry on with conversion.
620                 }
621                 rectNewArea = new Rectangle(0, 0, (int)s.Width, (int)s.Height);
622                 rectArea.X = 0;
623                 rectArea.Y = 0;
624                 rectArea.Width = rectNewArea.Width;
625                 rectArea.Height = rectNewArea.Height;
626             } else {
627                 rectNewArea = new Rectangle(0, 0, rectArea.Width, rectArea.Height);
628             }
629 
630             if (maxWidth != 0 && maxHeight != 0) {
631                 // If image is too big then shrink it. (Can't always use GetThumbnailImage because that might use embedded thumbnail).
632                 GMHelper.ScaleAreaToFit(ref rectNewArea, maxWidth, maxHeight);
633             }
634 
635             Bitmap bitmapNew = new Bitmap(rectNewArea.Width, rectNewArea.Height, PixelFormat.Format24bppRgb);
636             Graphics graphicsNew = Graphics.FromImage(bitmapNew);
637 
638             graphicsNew.DrawImage(image, rectNewArea, rectArea, GraphicsUnit.Pixel);
639             image.Dispose();
640 
641             // Find which format to save in. TODO: There must be a more elegant way!!
642             string extn = Path.GetExtension(fileName);
643             string filepart = Path.GetDirectoryName(fileName);
644             filepart += "\\" + Path.GetFileNameWithoutExtension(fileName);
645             ImageFormat imageFormat;
646             switch (extn.ToLower()) {
647                 case ".jpg":
648                 case ".jpeg":
649                     extn = ".jpg";
650                     imageFormat = ImageFormat.Jpeg;
651                     break;
652                 case ".gif":
653                     imageFormat = ImageFormat.Gif;
654                     break;
655                 case ".bmp":
656                     imageFormat = ImageFormat.Bmp;
657                     break;
658                 case ".tif":
659                 case ".tiff":
660                     // Tif's don't display in browsers, so convert to png.
661                     imageFormat = ImageFormat.Png;
662                     extn = ".png";
663                     break;
664                 case ".exif":
665                     imageFormat = ImageFormat.Exif;
666                     break;
667                 case ".png":
668                     imageFormat = ImageFormat.Png;
669                     break;
670                 default:
671                     imageFormat = ImageFormat.Jpeg;
672                     break;
673             }
674 
675             string filenameNew = filepart + extn;
676             string absFilenameNew = string.Concat(folder, filenameNew);
677             try {
678                 if (File.Exists(absFilename)) {
679                     // Delete the old file (e.g. if converting from tif to png)
680                     File.Delete(absFilename);
681                 }
682             } catch (Exception e) {
683                 fLogger.WriteError(string.Format("Caught exception while removing old bitmap file {0}", absFilename), e);
684             }
685             try {
686                 if (File.Exists(absFilenameNew)) {
687                     // Delete any existing file
688                     File.SetAttributes(absFilenameNew, FileAttributes.Normal);
689                     File.Delete(absFilenameNew);
690                 }
691                 bitmapNew.Save(absFilenameNew, imageFormat);
692             } catch (Exception e) {
693                 fLogger.WriteError(string.Format("Caught exception while writing bitmap file {0}", filenameNew), e);
694                 filenameNew = "";
695             }
696             graphicsNew.Dispose();
697             bitmapNew.Dispose();
698 
699             rectArea = rectNewArea;
700             return filenameNew;
701         }
702     }
703 }
704