示例#1
0
        public void CreatePDF(Stream stream)
        {
            var doc      = new GcPdfDocument();
            var font     = StandardFonts.Times;
            var fontSize = 12;
            // 1/2" margins all around (72 dpi is the default resolution used by GcPdf):
            var margin     = 72 / 2;
            var pageWidth  = doc.PageSize.Width;
            var pageHeight = doc.PageSize.Height;
            var cW         = pageWidth - margin * 2;
            // Text format for the chapter titles:
            var tlCaption = new TextLayout(72);

            tlCaption.DefaultFormat.Font      = font;
            tlCaption.DefaultFormat.FontSize  = fontSize + 4;
            tlCaption.DefaultFormat.Underline = true;
            tlCaption.MaxWidth      = pageWidth;
            tlCaption.MaxHeight     = pageHeight;
            tlCaption.MarginLeft    = tlCaption.MarginTop = tlCaption.MarginRight = tlCaption.MarginBottom = margin;
            tlCaption.TextAlignment = TextAlignment.Center;
            // Height of chapter caption (use a const for simplicity):
            const float captionH = 24;
            // Text layout for main document body (default GcPdf resolution is 72dpi):
            var tl = new TextLayout(72);

            tl.DefaultFormat.Font     = font;
            tl.DefaultFormat.FontSize = fontSize;
            tl.FirstLineIndent        = 72 / 2;
            tl.MaxWidth              = pageWidth;
            tl.MaxHeight             = pageHeight;
            tl.MarginLeft            = tl.MarginRight = tl.MarginBottom = margin;
            tl.MarginTop             = margin + captionH;
            tl.ColumnWidth           = cW * 0.3f;
            tl.TextAlignment         = TextAlignment.Justified;
            tl.AlignmentDelayToSplit = true;
            // Array of PageSplitArea's which control additional columns (1st column is controlled by
            // the 'main' TextLayout, for each additional one a PageSplitArea must be provided -
            // it will create and return a TextLayout that can then be used to render the column):
            var psas = new PageSplitArea[]
            {
                new PageSplitArea(tl)
                {
                    MarginLeft = tl.MarginLeft + (cW * 0.35f)
                },
                new PageSplitArea(tl)
                {
                    ColumnWidth = -cW * 0.3f
                }
            };
            // Split options to control splitting text between pages:
            TextSplitOptions tso = new TextSplitOptions(tl)
            {
                RestMarginTop            = margin,
                MinLinesInFirstParagraph = 2,
                MinLinesInLastParagraph  = 2
            };
            // Generate a number of "chapters", provide outline entry for each:
            const int NChapters = 20;

            doc.Pages.Add();
            for (int i = 0; i < NChapters; ++i)
            {
                // Print chapter header across all columns:
                string chapter = $"Chapter {i + 1}";
                tlCaption.Clear();
                tlCaption.Append(chapter);
                tlCaption.PerformLayout(true);
                doc.Pages.Last.Graphics.DrawTextLayout(tlCaption, PointF.Empty);
                // Add outline node for the chapter:
                doc.Outlines.Add(new OutlineNode(chapter, new DestinationFitV(doc.Pages.Count - 1, null)));
                //
                // Clear last chapter's text and add new chapter:
                tl.FirstLineIsStartOfParagraph = true;
                tl.LastLineIsEndOfParagraph    = true;
                tl.Clear();
                tl.Append(Common.Util.LoremIpsum(5, 7, 9, 15, 25));
                tl.PerformLayout(true);
                // Variable to hold last chapter end's bottom coord:
                float contentBottom = 0f;
                // Print the chapter:
                var tls = new TextLayoutSplitter(tl);
                while (true)
                {
                    var tlCol0 = tls.SplitAndBalance(psas, tso);
                    var g      = doc.Pages.Last.Graphics;
                    g.DrawTextLayout(tlCol0, PointF.Empty);
                    g.DrawTextLayout(psas[0].TextLayout, PointF.Empty);
                    g.DrawTextLayout(psas[1].TextLayout, PointF.Empty);
                    if (tls.SplitResult != SplitResult.Split)
                    {
                        // End of chapter, find out how much height left on page for next chapter:
                        contentBottom = tl.ContentY + tl.ContentHeight;
                        contentBottom = Math.Max(contentBottom, psas[0].TextLayout.ContentRectangle.Bottom);
                        contentBottom = Math.Max(contentBottom, psas[1].TextLayout.ContentRectangle.Bottom);
                        // Done printing chapter:
                        break;
                    }
                    // Continue printing chapter on new page:
                    psas[0].MarginTop = psas[1].MarginTop = margin;
                    doc.Pages.Add();
                }
                // Next chapter - find out if we have enough space left on current page to start new chapter:
                if (contentBottom + captionH < pageHeight * 0.8f)
                {
                    // Start new chapter on current page:
                    contentBottom      += pageHeight * 0.05f;
                    tlCaption.MarginTop = contentBottom;
                    tl.MarginTop        = psas[0].MarginTop = psas[1].MarginTop = contentBottom + captionH;
                }
                else if (i < NChapters - 1)
                {
                    // Start new chapter on new page:
                    tlCaption.MarginTop = margin;
                    tl.MarginTop        = psas[0].MarginTop = psas[1].MarginTop = margin + captionH;
                    doc.Pages.Add();
                }
            }
            // Done:
            doc.Save(stream);
        }
示例#2
0
        // Adds a word index to the end of the passed document:
        private void AddWordIndex(GcPdfDocument doc, IEnumerable <string> words)
        {
            var tStart = DateTime.Now;

            // Words and page indices where they occur, sorted on words:
            SortedDictionary <string, List <int> > index = new SortedDictionary <string, List <int> >();

            // Here the main loop building the index is on key words.
            // An alternative would be to loop over the pages.
            // Depending on the relative sizes of the keyword dictionary vs
            // the number of pages in the document, one or the other might be better,
            // but this is beyond the scope of this sample.
            foreach (string word in words)
            {
                bool wholeWord = word.IndexOf(' ') == -1;
                var  ft        = doc.FindText(new FindTextParams(word, wholeWord, false), OutputRange.All);
                var  pgs       = ft.Select(fp_ => fp_.PageIndex).Distinct();
                // A very simplistic way of also finding plurals:
                if (wholeWord && !word.EndsWith('s'))
                {
                    var ftpl = doc.FindText(new FindTextParams(word + "s", wholeWord, false), OutputRange.All);
                    pgs = pgs.Concat(ftpl.Select(fp_ => fp_.PageIndex).Distinct()).Distinct();
                }
                if (pgs.Any())
                {
                    var sorted = pgs.ToList();
                    sorted.Sort();
                    index.Add(word, sorted);
                }
            }

            // Prepare to render the index. The whole index is built
            // in a single TextLayout instance, set up to render it
            // in two columns per page.
            // The main rendering loop uses the TextLayout.SplitAndBalance method
            // using the approach demonstrated in BalancedColumns sample.
            // The complication here is that we need to associate a link to the
            // relevant page with each page number rendered, see linkIndices below.
            // Set up the TextLayout:
            const float margin     = 72;
            var         pageWidth  = doc.PageSize.Width;
            var         pageHeight = doc.PageSize.Height;
            var         cW         = pageWidth - margin * 2;
            // Caption (index letter) format:
            var tfCap = new TextFormat()
            {
                FontName = _fontFamily,
                FontBold = true,
                FontSize = 16,
                LineGap  = 24,
            };
            // Index word and pages format:
            var tfRun = new TextFormat()
            {
                FontName = _fontFamily,
                FontSize = 10,
            };
            // Page headers/footers:
            var tfHdr = new TextFormat()
            {
                FontName   = _fontFamily,
                FontItalic = true,
                FontSize   = 10,
            };
            // FirstLineIndent = -18 sets up hanging indent:
            var tl = new TextLayout(72)
            {
                FontCollection         = _fc,
                FirstLineIndent        = -18,
                MaxWidth               = pageWidth,
                MaxHeight              = pageHeight,
                MarginLeft             = margin,
                MarginRight            = margin,
                MarginBottom           = margin,
                MarginTop              = margin,
                ColumnWidth            = cW * 0.46f,
                TextAlignment          = TextAlignment.Leading,
                ParagraphSpacing       = 4,
                LineGapBeforeFirstLine = false,
            };

            // The list of text runs created for page numbers:
            List <Tuple <TextRun, int> > pgnumRuns = new List <Tuple <TextRun, int> >();
            // This loop builds the index on the TextLayout, saving the text runs
            // created for each page number rendered. Note that at this point
            // (prior to the PerformLayout(true) call) the text runs do not contain any info
            // about their code points and render locations, so we can only save the text runs here.
            // Later they will be used to add links to referenced pages in the PDF:
            char litera = ' ';

            foreach (KeyValuePair <string, List <int> > kvp in index)
            {
                var word        = kvp.Key;
                var pageIndices = kvp.Value;
                if (Char.ToUpper(word[0]) != litera)
                {
                    litera = Char.ToUpper(word[0]);
                    tl.Append($"{litera}\u2029", tfCap);
                }
                tl.Append(word, tfRun);
                tl.Append("  ", tfRun);
                for (int i = 0; i < pageIndices.Count; ++i)
                {
                    var from = pageIndices[i];
                    var tr   = tl.Append((from + 1).ToString(), tfRun);
                    pgnumRuns.Add(Tuple.Create(tr, from));
                    // We merge sequential pages into "..-M":
                    int k = i;
                    for (int j = i + 1; j < pageIndices.Count && pageIndices[j] == pageIndices[j - 1] + 1; ++j)
                    {
                        k = j;
                    }
                    if (k > i + 1)
                    {
                        tl.Append("-", tfRun);
                        var to = pageIndices[k];
                        tr = tl.Append((to + 1).ToString(), tfRun);
                        pgnumRuns.Add(Tuple.Create(tr, to));
                        // Fast forward:
                        i = k;
                    }
                    if (i < pageIndices.Count - 1)
                    {
                        tl.Append(", ", tfRun);
                    }
                    else
                    {
                        tl.AppendLine(tfRun);
                    }
                }
            }
            // This calculates the glyphs and lays out the whole index.
            // The tl.SplitAndBalance() call in the loop below does not require redoing the layout:
            tl.PerformLayout(true);

            //
            // Now we are ready to split and render the text layout, and also add links to page numbers.
            //

            // Split areas and options - see BalancedColumns for details:
            var psas = new PageSplitArea[] {
                new PageSplitArea(tl)
                {
                    MarginLeft = tl.MarginLeft + (cW * 0.54f)
                },
            };
            TextSplitOptions tso = new TextSplitOptions(tl)
            {
                KeepParagraphLinesTogether = true,
            };

            // First original code point index in the current column:
            int cpiStart = 0;
            // Max+1 original code point index in the current column:
            int cpiEnd = 0;
            // Current index in pgnumRuns:
            int pgnumRunsIdx = 0;

            // Split and render the index in 2 columns:
            for (var page = doc.Pages.Add(); ; page = doc.Pages.Add())
            {
                var g = page.Graphics;
                // Add a simple page header:
                g.DrawString($"Index generated by GcPdf on {tStart}", tfHdr,
                             new RectangleF(margin, 0, pageWidth - margin * 2, margin),
                             TextAlignment.Center, ParagraphAlignment.Center, false);
                // 'rest' will accept the text that did not fit on this page:
                var splitResult = tl.SplitAndBalance(psas, tso, out TextLayout rest);
                // Render text:
                g.DrawTextLayout(tl, PointF.Empty);
                g.DrawTextLayout(psas[0].TextLayout, PointF.Empty);
                // Add links from page numbers to pages:
                linkIndices(tl, page);
                linkIndices(psas[0].TextLayout, page);
                // Are we done yet?
                if (splitResult != SplitResult.Split)
                {
                    break;
                }
                tl = rest;
            }
            // Done:
            return;

            // Method to add links to actual pages over page numbers in the current column:
            void linkIndices(TextLayout tl_, Page page_)
            {
                cpiEnd += tl_.CodePointCount;
                for (; pgnumRunsIdx < pgnumRuns.Count; ++pgnumRunsIdx)
                {
                    var run     = pgnumRuns[pgnumRunsIdx];
                    var textRun = run.Item1;
                    int cpi     = textRun.CodePointIndex;
                    if (cpi >= cpiEnd)
                    {
                        break;
                    }
                    cpi -= cpiStart;
                    var rects = tl_.GetTextRects(cpi, textRun.CodePointCount);
                    System.Diagnostics.Debug.Assert(rects.Count > 0);
                    page_.Annotations.Add(new LinkAnnotation(rects[0].ToRectangleF(), new DestinationFit(run.Item2)));
                }
                cpiStart += tl_.CodePointCount;
            }
        }