public static void Generate(string inputDirectory, IOutputForm outputForm)
        {
            try {
                outputForm.WriteOutputMessage("Starting flashcard generation...");
                outputForm.WriteOutputMessage("");

                InputDirectory = inputDirectory;
                OutputDeckFile = new OutputDeckFile();

                ValidateInput();
                var itemsFile = new ItemsFile(inputDirectory);
                var deckFile = new DeckFile(inputDirectory);

                CreateOutputDirectory(deckFile);

                foreach (var card in deckFile.Cards) {
                    GenerateCardImages(card, itemsFile, deckFile, outputForm);
                }

                foreach (var template in deckFile.Templates) {
                    GenerateCardImages(template, itemsFile, deckFile, outputForm);
                }

                outputForm.WriteOutputMessage("");
                outputForm.WriteOutputMessage("Writing output deck file...");
                OutputDeckFile.WriteFile(OutputDirectory, deckFile.DeckName);

                outputForm.WriteOutputMessage("");
                outputForm.WriteOutputMessage("Done!", Color.Green);
            }
            catch (Exception exception) {
                outputForm.WriteOutputErrorMessage("");
                outputForm.WriteOutputErrorMessage(new string('=', 40));
                outputForm.WriteOutputErrorMessage("");
                outputForm.WriteOutputErrorMessage("ERROR: " + exception.Message);
                outputForm.WriteOutputErrorMessage("");
            }
        }
        private static void GenerateCardImageSideImage(XmlNode sideNode, XmlNode itemNode, ItemsFile itemsFile, Image baseImage, string fullOutputFileName)
        {
            var imageNode = sideNode.SelectSingleNode("image");
            string imageNameFormat = GeneratorXmlFile.GetAttributeValue(imageNode, "name");
            int borderWidth = GeneratorXmlFile.GetIntFromAttribute(imageNode, "border-width", 0);

            if (imageNameFormat == "") {
                throw new XmlException("\"image\" node does not have \"name\" attribute.");
            }

            string imageName = itemsFile.ApplyKeyValueToString(imageNameFormat, itemNode);
            string fullImageFileName = Path.Combine(itemsFile.BaseDirectory, imageName);

            var itemImage = Image.FromFile(fullImageFileName);
            itemImage = AddBorderToImage(itemImage, borderWidth);
            itemImage = ScaleImage(itemImage, new Size(baseImage.Width - 10, baseImage.Height - 10));

            using (var graphics = Graphics.FromImage(baseImage)) {
                var point = ComputeOrigin(baseImage.Size, itemImage.Size);
                var rectangle = new Rectangle(point, itemImage.Size);

                graphics.DrawImage(itemImage, rectangle);
            }

            baseImage.Save(fullOutputFileName, ImageFormat.Png);

            baseImage.Dispose();
        }
        private static void GenerateCardSideImage(XmlNode sideNode, int sideCounter, XmlNode cardNode, string cardID, ItemsFile itemsFile, DeckFile deckFile, IOutputForm outputForm)
        {
            var cardSideTypeID = DetermineCardSideType(sideNode);
            string baseImagePath = GetBaseImagePathForCard(sideNode, cardNode, deckFile);
            var baseImage = Image.FromFile(baseImagePath);

            foreach (var item in itemsFile.Items) {
                string keyValue = itemsFile.GetKeyValueFromItemNode(item);
                string outputFileName = BuildOutputFileName(cardID, keyValue, sideCounter);
                string fullOutputFileName = Path.Combine(OutputDirectory, deckFile.MediaDirectoryName, outputFileName);

                switch (cardSideTypeID) {
                    case CardSideType.Image:
                        outputForm.WriteOutputMessage(string.Format("   - Generating image card side file \"{0}\"", outputFileName));
                        GenerateCardImageSideImage(sideNode, item, itemsFile, (Image) baseImage.Clone(), fullOutputFileName);
                        break;

                    case CardSideType.Text:
                        outputForm.WriteOutputMessage(string.Format("   - Generating text card side file \"{0}\"", outputFileName));
                        GenerateCardTextSideImage(sideNode, item, (Image) baseImage.Clone(), fullOutputFileName);
                        break;

                    case CardSideType.Templated:
                        string templateID = GeneratorXmlFile.GetAttributeValue(sideNode.FirstChild, "id");
                        outputFileName = BuildOutputFileName(templateID, keyValue, 0);
                        break;
                }

                if (cardNode.Name == "card") {
                    OutputDeckFile.Add(cardID, keyValue, outputFileName);
                }
            }

            baseImage.Dispose();
        }
        private static void GenerateCardImages(XmlNode cardNode, ItemsFile itemsFile, DeckFile deckFile, IOutputForm outputForm)
        {
            string cardID = GeneratorXmlFile.GetAttributeValue(cardNode, "id");
            int sideCounter = 0;

            if (cardNode.Name == "card") {
                foreach (XmlNode sideNode in cardNode.SelectNodes("side")) {
                    sideCounter++;

                    outputForm.WriteOutputMessage(string.Format("Generating side {0} of card \"{1}\"", sideCounter, cardID));
                    GenerateCardSideImage(sideNode, sideCounter, cardNode, cardID, itemsFile, deckFile, outputForm);
                }
            }
            else {
                outputForm.WriteOutputMessage(string.Format("Generating template \"{1}\"", sideCounter, cardID));
                GenerateCardSideImage(cardNode, sideCounter, cardNode, cardID, itemsFile, deckFile, outputForm);
            }
        }