/// <summary> /// Copies content of the bookmark and adds it to the end of the specified node. /// The destination node can be in a different document. /// </summary> /// <param name="importer">Maintains the import context </param> /// <param name="srcBookmark">The input bookmark</param> /// <param name="dstNode">Must be a node that can contain paragraphs (such as a Story).</param> private static void AppendBookmarkedText(NodeImporter importer, Bookmark srcBookmark, CompositeNode dstNode) { // This is the paragraph that contains the beginning of the bookmark. Paragraph startPara = (Paragraph)srcBookmark.BookmarkStart.ParentNode; // This is the paragraph that contains the end of the bookmark. Paragraph endPara = (Paragraph)srcBookmark.BookmarkEnd.ParentNode; if ((startPara == null) || (endPara == null)) { throw new InvalidOperationException("Parent of the bookmark start or end is not a paragraph, cannot handle this scenario yet."); } // Limit ourselves to a reasonably simple scenario. if (startPara.ParentNode != endPara.ParentNode) { throw new InvalidOperationException("Start and end paragraphs have different parents, cannot handle this scenario yet."); } // We want to copy all paragraphs from the start paragraph up to (and including) the end paragraph, // Therefore the node at which we stop is one after the end paragraph. Node endNode = endPara.NextSibling; // This is the loop to go through all paragraph-level nodes in the bookmark. for (Node curNode = startPara; curNode != endNode; curNode = curNode.NextSibling) { // This creates a copy of the current node and imports it (makes it valid) in the context // Of the destination document. Importing means adjusting styles and list identifiers correctly. Node newNode = importer.ImportNode(curNode, true); // Now we simply append the new node to the destination. dstNode.AppendChild(newNode); } }
/// <summary> /// Copies content of the bookmark and adds it to the end of the specified node. /// The destination node can be in a different document. /// </summary> /// <param name="importer">Maintains the import context </param> /// <param name="srcBookmark">The input bookmark</param> /// <param name="dstNode">Must be a node that can contain paragraphs (such as a Story).</param> private static void AppendBookmarkedText(NodeImporter importer, Bookmark srcBookmark, CompositeNode dstNode) { // This is the paragraph that contains the beginning of the bookmark. Paragraph startPara = (Paragraph)srcBookmark.BookmarkStart.ParentNode; // This is the paragraph that contains the end of the bookmark. Paragraph endPara = (Paragraph)srcBookmark.BookmarkEnd.ParentNode; if ((startPara == null) || (endPara == null)) throw new InvalidOperationException("Parent of the bookmark start or end is not a paragraph, cannot handle this scenario yet."); // Limit ourselves to a reasonably simple scenario. if (startPara.ParentNode != endPara.ParentNode) throw new InvalidOperationException("Start and end paragraphs have different parents, cannot handle this scenario yet."); // We want to copy all paragraphs from the start paragraph up to (and including) the end paragraph, // Therefore the node at which we stop is one after the end paragraph. Node endNode = endPara.NextSibling; // This is the loop to go through all paragraph-level nodes in the bookmark. for (Node curNode = startPara; curNode != endNode; curNode = curNode.NextSibling) { // This creates a copy of the current node and imports it (makes it valid) in the context // Of the destination document. Importing means adjusting styles and list identifiers correctly. Node newNode = importer.ImportNode(curNode, true); // Now we simply append the new node to the destination. dstNode.AppendChild(newNode); } }
private CompositeNode SplitCompositeAtNode(CompositeNode baseNode, Node targetNode) { CompositeNode cloneNode = (CompositeNode)baseNode.Clone(false); Node node = targetNode; int currentPageNum = pageNumberFinder.GetPage(baseNode); // Move all nodes found on the next page into the copied node. Handle row nodes separately. if (baseNode.NodeType != NodeType.Row) { CompositeNode composite = cloneNode; if (baseNode.NodeType == NodeType.Section) { cloneNode = (CompositeNode)baseNode.Clone(true); Section section = (Section)cloneNode; section.Body.RemoveAllChildren(); composite = section.Body; } while (node != null) { Node nextNode = node.NextSibling; composite.AppendChild(node); node = nextNode; } } else { // If we are dealing with a row, we need to add dummy cells for the cloned row. int targetPageNum = pageNumberFinder.GetPage(targetNode); Node[] childNodes = baseNode.ChildNodes.ToArray(); foreach (Node childNode in childNodes) { int pageNum = pageNumberFinder.GetPage(childNode); if (pageNum == targetPageNum) { if (cloneNode.NodeType == NodeType.Row) { ((Row)cloneNode).EnsureMinimum(); } if (cloneNode.NodeType == NodeType.Cell) { ((Cell)cloneNode).EnsureMinimum(); } cloneNode.LastChild.Remove(); cloneNode.AppendChild(childNode); } else if (pageNum == currentPageNum) { cloneNode.AppendChild(childNode.Clone(false)); if (cloneNode.LastChild.NodeType != NodeType.Cell) { ((CompositeNode)cloneNode.LastChild).AppendChild( ((CompositeNode)childNode).FirstChild.Clone(false)); } } } } // Insert the split node after the original. baseNode.ParentNode.InsertAfter(cloneNode, baseNode); // Update the new page numbers of the base node and the cloned node, including its descendants. // This will only be a single page as the cloned composite is split to be on one page. int currentEndPageNum = pageNumberFinder.GetPageEnd(baseNode); pageNumberFinder.AddPageNumbersForNode(baseNode, currentPageNum, currentEndPageNum - 1); pageNumberFinder.AddPageNumbersForNode(cloneNode, currentEndPageNum, currentEndPageNum); foreach (Node childNode in cloneNode.GetChildNodes(NodeType.Any, true)) { pageNumberFinder.AddPageNumbersForNode(childNode, currentEndPageNum, currentEndPageNum); } return(cloneNode); }
/// <summary> /// Renders any node in a document to the path specified using the image save options. /// </summary> /// <param name="node">The node to render.</param> /// <param name="filePath">The path to save the rendered image to.</param> /// <param name="imageOptions">The image options to use during rendering. This can be null.</param> public void RenderNode(Node node, string filePath, ImageSaveOptions imageOptions) { if (imageOptions == null) { imageOptions = new ImageSaveOptions(FileFormatUtil.ExtensionToSaveFormat(Path.GetExtension(filePath))); } // Store the paper color to be used on the final image and change to transparent. // This will cause any content around the rendered node to be removed later on. Color savePaperColor = imageOptions.PaperColor; imageOptions.PaperColor = Color.Transparent; // There a bug which affects the cache of a cloned node. // To avoid this, we clone the entire document, including all nodes, // finding the matching node in the cloned document and rendering that instead. Document doc = (Document)node.Document.Clone(true); node = doc.GetChild(NodeType.Any, node.Document.GetChildNodes(NodeType.Any, true).IndexOf(node), true); // Create a temporary shape to store the target node in. This shape will be rendered to retrieve // the rendered content of the node. Shape shape = new Shape(doc, ShapeType.TextBox); Section parentSection = (Section)node.GetAncestor(NodeType.Section); // Assume that the node cannot be larger than the page in size. shape.Width = parentSection.PageSetup.PageWidth; shape.Height = parentSection.PageSetup.PageHeight; shape.FillColor = Color.Transparent; // Don't draw a surronding line on the shape. shape.Stroked = false; // Move up through the DOM until we find a suitable node to insert into a Shape // (a node with a parent can contain paragraphs, tables the same as a shape). Each parent node is cloned // on the way up so even a descendant node passed to this method can be rendered. Since we are working // with the actual nodes of the document we need to clone the target node into the temporary shape. Node currentNode = node; while (!(currentNode.ParentNode is InlineStory || currentNode.ParentNode is Story || currentNode.ParentNode is ShapeBase)) { CompositeNode parent = (CompositeNode)currentNode.ParentNode.Clone(false); currentNode = currentNode.ParentNode; parent.AppendChild(node.Clone(true)); node = parent; // Store this new node to be inserted into the shape. } // We must add the shape to the document tree to have it rendered. shape.AppendChild(node.Clone(true)); parentSection.Body.FirstParagraph.AppendChild(shape); // Render the shape to stream so we can take advantage of the effects of the ImageSaveOptions class. // Retrieve the rendered image and remove the shape from the document. MemoryStream stream = new MemoryStream(); ShapeRenderer renderer = shape.GetShapeRenderer(); renderer.Save(stream, imageOptions); shape.Remove(); Rectangle crop = renderer.GetOpaqueBoundsInPixels(imageOptions.Scale, imageOptions.HorizontalResolution, imageOptions.VerticalResolution); using (Bitmap renderedImage = new Bitmap(stream)) { Bitmap croppedImage = new Bitmap(crop.Width, crop.Height); croppedImage.SetResolution(imageOptions.HorizontalResolution, imageOptions.VerticalResolution); // Create the final image with the proper background color. using (Graphics g = Graphics.FromImage(croppedImage)) { g.Clear(savePaperColor); g.DrawImage(renderedImage, new Rectangle(0, 0, croppedImage.Width, croppedImage.Height), crop.X, crop.Y, crop.Width, crop.Height, GraphicsUnit.Pixel); croppedImage.Save(filePath); } } }