Ejemplo n.º 1
0
        //BottomMarginInMillimeters = 0,
        //TopMarginInMillimeters = 0,
        //LeftMarginInMillimeters = 0,
        //RightMarginInMillimeters = 0,
        //EnableGraphite = true,
        //Landscape = landscape,
        //InputHtmlPath = inputHtmlPath,
        //OutputPdfPath = tempOutput.Path,
        //PageSizeName = paperSizeName
        void SetArguments(StringBuilder bldr, PdfMakingSpecs specs)
        {
            bldr.AppendFormat("\"{0}\" \"{1}\"", specs.InputHtmlPath, specs.OutputPdfPath);
            bldr.Append(" --quiet");             // turn off its progress dialog (BL-3721)
            bldr.Append(" -B 0 -T 0 -L 0 -R 0");
            if (specs.PrintWithFullBleed)
            {
                ConfigureFullBleedPageSize(bldr, specs);
            }
            else if (specs.PaperSizeName == "USComic")
            {
                bldr.Append($" -h {USComicPortraitHeight} -w {USComicPortraitWidth}");
            }
            else
            {
                var match = Regex.Match(specs.PaperSizeName, @"^(cm|in)(\d+)$",
                                        RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
                if (match.Success)
                {
                    // Irregular (square) paper size
                    var size = int.Parse(match.Groups[2].Value);
                    if (match.Groups[1].Value == "in")
                    {
                        size = (int)(size * 25.4);                          // convert from inches to millimeters
                    }
                    else
                    {
                        size = size * 10;                         // convert from cm to mm
                    }
                    bldr.AppendFormat(" -h {0} -w {0}", size);
                }
                else
                {
                    bldr.AppendFormat(" -s {0}", specs.PaperSizeName);
                    if (specs.Landscape)
                    {
                        bldr.Append(" -Landscape");
                    }
                }
            }

            bldr.Append(" --graphite");
            if (specs.SaveMemoryMode)
            {
                bldr.Append(" --reduce-memory-use");
            }
        }
Ejemplo n.º 2
0
        private static void ConfigureFullBleedPageSize(StringBuilder bldr, PdfMakingSpecs specs)
        {
            // We will make a non-standard page size that is 6mm bigger in each dimension than the size indicated
            // by the paperSizeName. Unfortunately doing that means we can't just pass the name, we have to figure
            // out the size.
            double height;
            double width;

            switch (specs.PaperSizeName.ToLowerInvariant())
            {
            case "a5":
                height = A4PortraitWidth + bleedExtra;
                // we floor because that actually gives us the 148mm that is official
                width = Math.Floor(A4PortraitHeight / 2) + bleedExtra;
                break;

            case "a4":
                height = A4PortraitHeight + bleedExtra;
                width  = A4PortraitWidth + bleedExtra;
                break;

            case "a3":
                height = A3PortraitHeight + bleedExtra;
                width  = A3PortraitWidth + bleedExtra;
                break;

            case "uscomic":
                height = USComicPortraitHeight + bleedExtra;
                width  = USComicPortraitWidth + bleedExtra;
                break;

            default:
                throw new ArgumentException("Full bleed printing of paper sizes other than A5, A4, A3, and USComic is not yet implemented");
            }

            if (specs.Landscape)
            {
                var temp = height;
                height = width;
                width  = temp;
            }

            bldr.Append($" -h {height} -w {width}");
        }
Ejemplo n.º 3
0
        public void MakePdf(PdfMakingSpecs specs, Control owner, BackgroundWorker worker,
                            DoWorkEventArgs doWorkEventArgs)
        {
            _worker = worker;
#if !__MonoCS__
            // Mono doesn't current provide System.Printing.  Leave the 'if' here to emphasize the
            // system specific nature of the following check.
            if (Platform.IsWindows)
            {
                // Check whether we have a default printer set (or for that matter, any printers).
                // Gecko on Windows requires a default printer for any print operation, even one
                // to a file.  See https://jira.sil.org/browse/BL-1237.
                string errorMessage = null;
                System.Printing.LocalPrintServer printServer = null;
                try
                {
                    printServer = new System.Printing.LocalPrintServer();
                }
                catch (Exception)                 // System.Printing.PrintQueueException isn't in our System.Printing assembly, so... using Exception
                {
                    // http://issues.bloomlibrary.org/youtrack/issue/BL-4060
                    Logger.WriteEvent("reproduced BL-4060 when trying to create LocalPrinterServer");
                }
                if (printServer == null || !printServer.GetPrintQueues().Any())
                {
                    errorMessage = GetNoDefaultPrinterErrorMessage();
                }
                else
                {
                    System.Printing.PrintQueue defaultPrinter;
                    // BL-2535 it's possible get past the above printQueues.Any() but then get
                    // a System.Printing.PrintQueueException exception with "Access Denied" error here, if
                    // the default printer for some reason is no longer "allowed".
                    try
                    {
                        defaultPrinter = System.Printing.LocalPrintServer.GetDefaultPrintQueue();

                        if (defaultPrinter == null || String.IsNullOrEmpty(defaultPrinter.FullName))
                        {
                            errorMessage = GetNoDefaultPrinterErrorMessage();
                        }
                    }
                    catch (Exception error)                    // System.Printing.PrintQueueException isn't in our System.Printing assembly, so... using Exception
                    {
                        defaultPrinter = null;
                        errorMessage   = L10NSharp.LocalizationManager.GetString(@"PublishTab.PDF.Error.PrinterError",
                                                                                 "Bloom requires access to a printer in order to make a PDF, even though you are not printing.  Windows gave this error when Bloom tried to access the default printer: {0}",
                                                                                 @"Error message displayed in a message dialog box");
                        errorMessage = string.Format(errorMessage, error.Message);
                    }
                }

                if (errorMessage != null)
                {
                    var exception = new ApplicationException(errorMessage);
                    // Note that if we're being run by a BackgroundWorker, it will catch the exception.
                    // If not, but the caller provides a DoWorkEventArgs, pass the exception through
                    // that object rather than throwing it.
                    if (worker != null || doWorkEventArgs == null)
                    {
                        throw exception;
                    }
                    doWorkEventArgs.Result = exception;
                    return;
                }
            }
#endif
            if (_worker != null)
            {
                _worker.ReportProgress(0, L10NSharp.LocalizationManager.GetString(@"PublishTab.PdfMaker.MakingFromHtml",
                                                                                  "Making PDF from HTML",
                                                                                  @"Message displayed in a progress report dialog box"));
            }

            var    runner = new CommandLineRunner();
            string exePath;
            var    bldr = new StringBuilder();
            // Codebase is reliable even when Resharper copies the EXE somewhere else for testing.
            var execDir       = BloomFileLocator.GetCodeBaseFolder();
            var fromDirectory = String.Empty;
            var filePath      = Path.Combine(execDir, "BloomPdfMaker.exe");
            if (!RobustFile.Exists(filePath))
            {
                var msg = LocalizationManager.GetString("InstallProblem.BloomPdfMaker",
                                                        "A component of Bloom, BloomPdfMaker.exe, seems to be missing. This prevents previews and printing. Antivirus software sometimes does this. You may need technical help to repair the Bloom installation and protect this file from being deleted again.");
                throw new FileNotFoundException(msg, "BloomPdfMaker.exe");                 // must be this class to trigger the right reporting mechanism.
            }
            if (Platform.IsMono)
            {
                exePath = Path.ChangeExtension(filePath, "sh");
            }
            else
            {
                exePath = filePath;
            }

            SetArguments(bldr, specs);
            var arguments = bldr.ToString();
            var progress  = new NullProgress();
            var res       = runner.Start(exePath, arguments, Encoding.UTF8, fromDirectory, 3600, progress,
                                         ProcessGeckofxReporting);
            if (res.DidTimeOut || !RobustFile.Exists(specs.OutputPdfPath))
            {
                Logger.WriteEvent(@"***ERROR PDF generation failed: res.StandardOutput = " + res.StandardOutput);

                var msg = L10NSharp.LocalizationManager.GetString(@"PublishTab.PDF.Error.Failed",
                                                                  "Bloom was not able to create the PDF file ({0}).{1}{1}Details: BloomPdfMaker (command line) did not produce the expected document.",
                                                                  @"Error message displayed in a message dialog box. {0} is the filename, {1} is a newline character.");

                // This message string is intentionally separate because it was added after the previous string had already been localized in most languages.
                // It's not useful to add if we're already in save memory mode.
                var msg2 = specs.SaveMemoryMode ? "" : L10NSharp.LocalizationManager.GetString(@"PublishTab.PDF.Error.TrySinglePage",
                                                                                               "The book's images might have exceeded the amount of RAM memory available. Please turn on the \"Use Less Memory\" option which is slower but uses less memory.",
                                                                                               @"Error message displayed in a message dialog box") + Environment.NewLine;

                var fullMsg = String.Format(msg, specs.OutputPdfPath, Environment.NewLine) + Environment.NewLine +
                              msg2 + res.StandardOutput;

                var except = new ApplicationException(fullMsg);
                // Note that if we're being run by a BackgroundWorker, it will catch the exception.
                // If not, but the caller provides a DoWorkEventArgs, pass the exception through
                // that object rather than throwing it.
                if (worker != null || doWorkEventArgs == null)
                {
                    throw except;
                }
                else
                {
                    doWorkEventArgs.Result = except;
                }
            }
        }
Ejemplo n.º 4
0
        ///  <summary>
        ///
        ///  </summary>
        /// <param name="specs">All the information about what sort of PDF file to make where</param>
        /// <param name="worker">If not null, the Background worker which is running this task, and may be queried to determine whether a cancel is being attempted</param>
        /// <param name="doWorkEventArgs">The event passed to the worker when it was started. If a cancel is successful, it's Cancel property should be set true.</param>
        /// <param name="owner">A control which can be used to invoke parts of the work which must be done on the ui thread.</param>
        public void MakePdf(PdfMakingSpecs specs, BackgroundWorker worker, DoWorkEventArgs doWorkEventArgs, Control owner)
        {
            // Try up to 4 times. This is a last-resort attempt to handle BL-361.
            // Most likely that was caused by a race condition in MakePdfUsingGeckofxHtmlToPdfComponent.MakePdf,
            // but as it was an intermittent problem and we're not sure that was the cause, this might help.
            for (int i = 0; i < 4; i++)
            {
                new MakePdfUsingGeckofxHtmlToPdfProgram().MakePdf(specs,
                                                                  owner, worker, doWorkEventArgs);

                if (doWorkEventArgs.Cancel || (doWorkEventArgs.Result != null && doWorkEventArgs.Result is Exception))
                {
                    return;
                }
                if (RobustFile.Exists(specs.OutputPdfPath))
                {
                    break;                     // normally the first time
                }
            }
            if (!RobustFile.Exists(specs.OutputPdfPath) && owner != null)
            {
                // Should never happen, but...
                owner.Invoke((Action)(() =>
                {
                    // Review: should we localize this? Hopefully the user never sees it...don't want to increase burden on localizers...
                    MessageBox.Show(
                        "Bloom unexpectedly failed to create the PDF. If this happens repeatedy please report it to the developers. Probably it will work if you just try again.",
                        "Pdf creation failed", MessageBoxButtons.OK);
                }));
                doWorkEventArgs.Result = MakingPdfFailedException.CreatePdfException();
                return;
            }

            try
            {
                // Shrink the PDF file, especially if it has large color images.  (BL-3721)
                // Also if the book is full bleed we need to remove some spurious pages.
                // Removing spurious pages must be done BEFORE we switch pages around to make a booklet!
                // Note: previously compression was the last step, after making a booklet. We moved it before for
                // the reason above. Seems like it would also have performance benefits, if anything, to shrink
                // the file before manipulating it further. Just noting it in case there are unexpected issues.
                var fixPdf = new ProcessPdfWithGhostscript(ProcessPdfWithGhostscript.OutputType.DesktopPrinting, worker);
                fixPdf.ProcessPdfFile(specs.OutputPdfPath, specs.OutputPdfPath, specs.BookIsFullBleed);
                if (specs.BookletPortion != PublishModel.BookletPortions.AllPagesNoBooklet || specs.PrintWithFullBleed)
                {
                    //remake the pdf by reording the pages (and sometimes rotating, shrinking, etc)
                    MakeBooklet(specs);
                }

                // Check that we got a valid, readable PDF.
                // If we get a reliable fix to BL-932 we can take this out altogether.
                // It's probably redundant, since the compression process would probably fail with this
                // sort of corruption, and we are many generations beyond gecko29 where we observed it.
                // However, we don't have data to reliably reproduce the BL-932, and the check doesn't take
                // long, so leaving it in for now.
                CheckPdf(specs.OutputPdfPath);
            }
            catch (KeyNotFoundException e)
            {
                // This is characteristic of BL-932, where Gecko29 fails to make a valid PDF, typically
                // because the user has embedded a really huge image, something like 4000 pixels wide.
                // We think it could also happen with a very long book or if the user is short of memory.
                // The resulting corruption of the PDF file takes the form of a syntax error in an embedded
                // object so that the parser finds an empty string where it expected a 'generationNumber'
                // (currently line 106 of Parser.cs). This exception is swallowed but leads to an empty
                // externalIDs dictionary in PdfImportedObjectTable, and eventually a new exception trying
                // to look up an object ID at line 121 of that class. We catch that exception here and
                // suggest possible actions the user can take until we find a better solution.
                SIL.Reporting.ErrorReport.NotifyUserOfProblem(e,
                                                              LocalizationManager.GetString("PublishTab.PdfMaker.BadPdf", "Bloom had a problem making a PDF of this book. You may need technical help or to contact the developers. But here are some things you can try:")
                                                              + Environment.NewLine + "- "
                                                              + LocalizationManager.GetString("PublishTab.PdfMaker.TryRestart", "Restart your computer and try this again right away")
                                                              + Environment.NewLine + "- "
                                                              +
                                                              LocalizationManager.GetString("PublishTab.PdfMaker.TrySmallerImages",
                                                                                            "Replace large, high-resolution images in your document with lower-resolution ones")
                                                              + Environment.NewLine + "- "
                                                              + LocalizationManager.GetString("PublishTab.PdfMaker.TryMoreMemory", "Try doing this on a computer with more memory"));

                RobustFile.Move(specs.OutputPdfPath, specs.OutputPdfPath + "-BAD");
                doWorkEventArgs.Result = MakingPdfFailedException.CreatePdfException();
            }
        }
Ejemplo n.º 5
0
        private void MakeBooklet(PdfMakingSpecs specs)
        {
            //TODO: we need to let the user chose the paper size, as they do in PdfDroplet.
            //For now, just assume a size double the original

            var incomingPaperSize = specs.PaperSizeName;

            PageSize pageSize;

            switch (incomingPaperSize)
            {
            case "A3":
                pageSize = PageSize.A2;
                break;

            case "A4":
                pageSize = PageSize.A3;
                break;

            case "A5":
                pageSize = PageSize.A4;
                break;

            case "A6":
                pageSize = PageSize.A5;
                break;

            case "B5":
                pageSize = PageSize.B4;
                break;

            case "Letter":
                pageSize = PageSize.Ledger;
                break;

            case "HalfLetter":
                pageSize = PageSize.Letter;
                break;

            case "QuarterLetter":
                pageSize = PageSize.Statement;                          // ?? Wikipedia says HalfLetter is aka Statement
                break;

            case "Legal":
                pageSize = PageSize.Legal;                        //TODO... what's reasonable?
                break;

            case "HalfLegal":
                pageSize = PageSize.Legal;
                break;

            case "Cm13":
                pageSize = PageSize.A3;
                break;

            case "USComic":
                pageSize = PageSize.A3;                         // Ledger would work as well.
                break;

            default:
                throw new ApplicationException("PdfMaker.MakeBooklet() does not contain a map from " + incomingPaperSize + " to a PdfSharp paper size.");
            }

            using (var incoming = new TempFile())
            {
                RobustFile.Delete(incoming.Path);
                RobustFile.Move(specs.OutputPdfPath, incoming.Path);

                LayoutMethod method;
                switch (specs.BooketLayoutMethod)
                {
                case PublishModel.BookletLayoutMethod.NoBooklet:
                    method = new NullLayoutMethod(specs.PrintWithFullBleed ? 3 : 0);
                    break;

                case PublishModel.BookletLayoutMethod.SideFold:
                    // To keep the GUI simple, we assume that A6 page size for booklets
                    // implies 4up printing on A4 paper.  This feature was requested by
                    // https://jira.sil.org/browse/BL-1059 "A6 booklets should print 4
                    // to an A4 sheet".  The same is done for QuarterLetter booklets
                    // printing on Letter size sheets.
                    if (incomingPaperSize == "A6")
                    {
                        method   = new SideFold4UpBookletLayouter();
                        pageSize = PageSize.A4;
                    }
                    else if (incomingPaperSize == "QuarterLetter")
                    {
                        method   = new SideFold4UpBookletLayouter();
                        pageSize = PageSize.Letter;
                    }
                    else if (incomingPaperSize == "Cm13")
                    {
                        method = new Square6UpBookletLayouter();
                    }
                    else
                    {
                        method = new SideFoldBookletLayouter();
                    }
                    break;

                case PublishModel.BookletLayoutMethod.CutAndStack:
                    method = new CutLandscapeLayout();
                    break;

                case PublishModel.BookletLayoutMethod.Calendar:
                    method = new CalendarLayouter();
                    break;

                default:
                    throw new ArgumentOutOfRangeException("booketLayoutMethod");
                }
                var paperTarget = new PaperTarget("ZZ" /*we're not displaying this anyhwere, so we don't need to know the name*/, pageSize);
                var pdf         = XPdfForm.FromFile(incoming.Path);        //REVIEW: this whole giving them the pdf and the file too... I checked once and it wasn't wasting effort...the path was only used with a NullLayout option
                method.Layout(pdf, incoming.Path, specs.OutputPdfPath, paperTarget, specs.LayoutPagesForRightToLeft, ShowCropMarks);
            }
        }