/// <summary> /// Parse preabule and fill class properties by parsed values /// </summary> /// <param name="preambule">Preambule node</param> public void Parse(Node preambule) { if (preambule.Type != "preambule") throw new ArgumentException("Function accepts only preambule node type."); // process preambule nodes foreach (Node node in preambule.Children) { switch (node.Type) { // title page settings case "author": case "title": case "date": case "institute": TitlepageSettings[node.Type] = node.Children; break; case "graphicspath": foreach (Node child in node.Children) { if (child.Type == "path" && (child.Content as string).Length > 0) { string path = Path.Combine(InputDir, child.Content as string); // add path only if directory exists if(Directory.Exists(path)) GraphicsPath.Add(path); } } break; case "usepackage": if (node.Content as string == "inputenc" && node.OptionalParams != "utf8") Messenger.Instance.SendMessage("Unsupported code page, some characters may be broken", Contract.MessageLevel.WARNING); break; // unknown or invalid node -> ignore default: break; } } }
/// <summary> /// Generate table from its node /// </summary> /// <param name="tableNode">Table node</param> /// <param name="tableShape">output - Shape of generated table (used for reshaper), null if no table was generated</param> /// <returns>true if completed; false if paused</returns> private bool GenerateTable(Node tableNode, out PowerPoint.Shape tableShape) { int rows = 0, cols = 0; tableShape = null; TabularSettings settings = TabularSettings.Parse(tableNode.Content as string); cols = settings.Columns.Count; // count table rows foreach (Node node in tableNode.Children) { if (node.Type == "tablerow") rows++; } // counted number of rows can be exactly one row greater than actual value (last row is empty) if (cols == 0 || rows == 0) // no columns or rows -> don't create table return true; // create table shape with "rows - 1" rows but at least one row; also create table with extreme width so we can resize it down tableShape = _slide.Shapes.AddTable(((rows - 1) > 0 ? rows - 1 : rows), cols, 36.0f, _bottomShapeBorder + 5.0f, cols * 1000.0f); // style without background and borders tableShape.Table.ApplyStyle("2D5ABB26-0587-4C30-8999-92F81FD0307C"); int rowCounter = 0, columnCounter = 0; Stack<Node> nodes = new Stack<Node>(); Node currentNode; PowerPoint.Shape shape; // cell shape // skip expanding children to stack bool skip = false; // pause processing variables bool paused = false; int pausedAfter = 0; foreach (Node node in tableNode.Children) { columnCounter = 0; if (node.Type == "tablerow") { rowCounter++; // check if we will generate last row if (rowCounter == rows && rowCounter != 1) { if (node.Children.Count == 1 && node.Children[0].Children.Count == 0) continue; else tableShape.Table.Rows.Add(); } foreach (Node rowcontent in node.Children) { if (rowcontent.Type == "tablecolumn" || rowcontent.Type == "tablecolumn_merged") { columnCounter++; if (columnCounter > cols) throw new DocumentBuilderException("Invalid table definition."); // get cell shape shape = tableShape.Table.Cell(rowCounter, columnCounter).Shape; // set cell alignment switch (settings.Columns[columnCounter-1].alignment) { case 'l': shape.TextFrame2.TextRange.ParagraphFormat.Alignment = MsoParagraphAlignment.msoAlignLeft; break; case 'c': shape.TextFrame2.TextRange.ParagraphFormat.Alignment = MsoParagraphAlignment.msoAlignCenter; break; case 'r': shape.TextFrame2.TextRange.ParagraphFormat.Alignment = MsoParagraphAlignment.msoAlignRight; break; case 'p': shape.TextFrame2.TextRange.ParagraphFormat.Alignment = MsoParagraphAlignment.msoAlignJustify; break; default: break; } _format.Invalidate(); // copy column content to stack foreach (Node item in rowcontent.Children.Reverse<Node>()) { nodes.Push(item); } // process nodes on stack while (nodes.Count != 0) { currentNode = nodes.Pop(); skip = false; // process node depending on its type switch (currentNode.Type) { case "string": _format.AppendText(shape, currentNode.Content as string); break; case "paragraph": _format.AppendText(shape, "\r"); break; case "pause": if (!paused && Pause()) { paused = true; if (columnCounter == 1 && shape.TextFrame2.TextRange.Text.Length == 0) pausedAfter = rowCounter - 1; else pausedAfter = rowCounter; } break; case "numberedlist": case "bulletlist": case "descriptionlist": case "image": case "table": if (!Settings.Instance.NestedAsText) { skip = true; _postProcessing.Enqueue(currentNode); } break; case "today": shape.TextFrame.TextRange.InsertDateTime(PowerPoint.PpDateTimeFormat.ppDateTimeFigureOut, MsoTriState.msoTrue); break; default: // other -> check for simple formats SimpleTextFormat(nodes, currentNode); break; } if (currentNode.Children == null || skip) continue; // push child nodes to stack foreach (Node item in currentNode.Children.Reverse<Node>()) { nodes.Push(item); } } if (rowcontent.Type == "tablecolumn") { if (columnCounter == 1 && settings.Borders.Contains(0)) // first column check also for border with index 0 (left border) { tableShape.Table.Rows[rowCounter].Cells[columnCounter].Borders[PowerPoint.PpBorderType.ppBorderLeft].ForeColor.RGB = 0x0; tableShape.Table.Rows[rowCounter].Cells[columnCounter].Borders[PowerPoint.PpBorderType.ppBorderLeft].DashStyle = MsoLineDashStyle.msoLineSolid; } if (settings.Borders.Contains(columnCounter)) // for every column set right border { tableShape.Table.Rows[rowCounter].Cells[columnCounter].Borders[PowerPoint.PpBorderType.ppBorderRight].ForeColor.RGB = 0x0; tableShape.Table.Rows[rowCounter].Cells[columnCounter].Borders[PowerPoint.PpBorderType.ppBorderRight].DashStyle = MsoLineDashStyle.msoLineSolid; } } // merge cells if (rowcontent.Type == "tablecolumn_merged") { // merge cells here and increment columnCounter depending on number of merged cells string tmp = rowcontent.Content as string; int merge_count; if (int.TryParse(tmp.Trim(), out merge_count)) { // merge cells if(merge_count > 1) tableShape.Table.Cell(rowCounter, columnCounter).Merge(tableShape.Table.Cell(rowCounter, columnCounter + merge_count - 1)); TabularSettings mset = TabularSettings.Parse(rowcontent.OptionalParams, true); // left border if (mset.Borders.Contains(0)) { tableShape.Table.Cell(rowCounter, columnCounter).Borders[PowerPoint.PpBorderType.ppBorderLeft].ForeColor.RGB = 0x0; tableShape.Table.Cell(rowCounter, columnCounter).Borders[PowerPoint.PpBorderType.ppBorderLeft].DashStyle = MsoLineDashStyle.msoLineSolid; } // right border if (mset.Borders.Contains(1)) { tableShape.Table.Cell(rowCounter, columnCounter + merge_count - 1).Borders[PowerPoint.PpBorderType.ppBorderRight].ForeColor.RGB = 0x0; tableShape.Table.Cell(rowCounter, columnCounter + merge_count - 1).Borders[PowerPoint.PpBorderType.ppBorderRight].DashStyle = MsoLineDashStyle.msoLineSolid; } // set cell alignment switch (mset.Columns[0].alignment) { case 'l': shape.TextFrame2.TextRange.ParagraphFormat.Alignment = MsoParagraphAlignment.msoAlignLeft; break; case 'c': shape.TextFrame2.TextRange.ParagraphFormat.Alignment = MsoParagraphAlignment.msoAlignCenter; break; case 'r': shape.TextFrame2.TextRange.ParagraphFormat.Alignment = MsoParagraphAlignment.msoAlignRight; break; case 'p': shape.TextFrame2.TextRange.ParagraphFormat.Alignment = MsoParagraphAlignment.msoAlignJustify; break; default: break; } // skip merged columns columnCounter += merge_count - 1; } } } } } else if(node.Type == "hline") { if (rowCounter == 0) { tableShape.Table.Rows[1].Cells.Borders[PowerPoint.PpBorderType.ppBorderTop].ForeColor.RGB = 0x0; tableShape.Table.Rows[1].Cells.Borders[PowerPoint.PpBorderType.ppBorderTop].DashStyle = MsoLineDashStyle.msoLineSolid; } else { tableShape.Table.Rows[rowCounter].Cells.Borders[PowerPoint.PpBorderType.ppBorderBottom].ForeColor.RGB = 0x0; tableShape.Table.Rows[rowCounter].Cells.Borders[PowerPoint.PpBorderType.ppBorderBottom].DashStyle = MsoLineDashStyle.msoLineSolid; } } else if (node.Type == "cline") { Regex regex = new Regex(@"^([0-9]+)-([0-9]+)$", RegexOptions.IgnoreCase); string range = node.Content as string; Match match = regex.Match(range.Trim()); if (match.Success) { int x, y; if (int.TryParse(match.Groups[1].Value, out x) && int.TryParse(match.Groups[2].Value, out y)) { for (int i = Math.Min(x,y); i <= Math.Max(x,y); i++) { if (rowCounter == 0) { tableShape.Table.Rows[1].Cells[i].Borders[PowerPoint.PpBorderType.ppBorderTop].ForeColor.RGB = 0x0; tableShape.Table.Rows[1].Cells[i].Borders[PowerPoint.PpBorderType.ppBorderTop].DashStyle = MsoLineDashStyle.msoLineSolid; } else { tableShape.Table.Rows[rowCounter].Cells[i].Borders[PowerPoint.PpBorderType.ppBorderBottom].ForeColor.RGB = 0x0; tableShape.Table.Rows[rowCounter].Cells[i].Borders[PowerPoint.PpBorderType.ppBorderBottom].DashStyle = MsoLineDashStyle.msoLineSolid; } } } } } } // resize table Misc.AutoFitColumn(tableShape, settings); // if processing was paused remove all lines after pause commands (columns are not supported yet) if (paused) { if (pausedAfter == 0) { tableShape.Delete(); tableShape = null; } else { for (int i = tableShape.Table.Rows.Count; i > pausedAfter; i--) { tableShape.Table.Rows[i].Delete(); } } return false; } return true; }
/// <summary> /// Insert image from its node /// </summary> /// <param name="imageNode">Image node</param> /// <param name="imageShape">output - Shape of inserted image (used for reshaper), null if no image was inserted</param> private void GenerateImage(Node imageNode, out PowerPoint.Shape imageShape) { imageShape = null; // find inserted image string imagePath = Misc.FindImage(imageNode.Content as string, _preambuleSettings.GraphicsPath); if (imagePath == null) { throw new DocumentBuilderException("Image '" + imageNode.Content + "' not found!"); } try // just to be sure { imageShape = _slide.Shapes.AddPicture(imagePath, MsoTriState.msoFalse, MsoTriState.msoTrue, 36.0f, _bottomShapeBorder + 5.0f); } catch (Exception) { // throw image not found exception throw new DocumentBuilderException("Image '" + imageNode.Content + "' not found!"); } float width = 0, height = 0, scale = 0; // parse optional parameters string[] optParams = imageNode.OptionalParams.Replace(" ", "").Split(new Char[] {','}, StringSplitOptions.RemoveEmptyEntries); foreach (string item in optParams) { string[] pair = item.Split('='); switch (pair[0]) { case "width": if (pair.Length > 1) width = Misc.ParseLength(pair[1]); break; case "height": if (pair.Length > 1) height = Misc.ParseLength(pair[1]); break; case "scale": if (pair.Length > 1) float.TryParse(pair[1], out scale); break; default: break; } } // resize image according to optinal params if (width != 0 && height != 0) // width and height was set in optinal parameters { imageShape.LockAspectRatio = MsoTriState.msoFalse; imageShape.Height = height; imageShape.Width = width; } else if (width != 0) // only width was set { imageShape.LockAspectRatio = MsoTriState.msoTrue; imageShape.Width = width; } else if (height != 0) // only height was set { imageShape.LockAspectRatio = MsoTriState.msoTrue; imageShape.Height = height; } else if (scale != 0) // scale was set { imageShape.LockAspectRatio = MsoTriState.msoTrue; imageShape.Width *= scale; } }
/// <summary> /// Process simple text formatting /// </summary> /// <param name="nodes">Nodes stack - used for pushing format rollback node</param> /// <param name="node">Current node</param> private void SimpleTextFormat(Stack<Node> nodes, Node node) { Node rollbackNode = new Node("__format_pop"); switch (node.Type) { case "bold": case "italic": case "underline": case "smallcaps": case "typewriter": case "color": case "tiny": case "scriptsize": case "footnotesize": case "small": case "normalsize": case "large": case "Large": case "LARGE": case "huge": case "Huge": // check overlay settings if (Misc.ShowOverlay(_passNumber, node.OverlayList, ref _maxPass)) { _format.ModifyFormat(node); nodes.Push(rollbackNode); } break; case "__format_pop": // special node -> pop formatting from stack _format.RollBackFormat(); break; default: // unknown node -> ignore break; } }
/// <summary> /// Build slide content /// </summary> /// <param name="slide">Slide in PowerPoint presentation</param> /// <param name="slideNode">Node containing content of slide</param> /// <param name="passNumber">Number of current pass (used for overlays)</param> /// <param name="pauseCounter">Number of used pauses</param> /// <param name="paused">output - processing of slide content was paused</param> /// <returns>true if slide is complete; false if needs another pass</returns> public bool BuildSlide(PowerPoint.Slide slide, Node slideNode, int passNumber, int pauseCounter, out bool paused) { _slide = slide; _passNumber = passNumber; _pauseCounter = pauseCounter; // copy title settings _titlesettings = new Dictionary<string,List<Node>>(_preambuleSettings.TitlepageSettings); _format = new TextFormat(_baseFontSize); _postProcessing = new Queue<Node>(); if (!_called) { // because title is processed before slide and can contain pause command, we need to setup internal counters according to title passed at first processing _localPauseCounter = pauseCounter; _localPauseCounterStart = pauseCounter; _maxPass = passNumber; _called = true; } else { _localPauseCounter = _localPauseCounterStart; } _bottomShapeBorder = 0.0f; UpdateBottomShapeBorder(); paused = !ProcessSlideContent(slideNode); if (!Settings.Instance.NestedAsText) // extract nested elements { _localPauseCounter = int.MinValue; // ignore pauses in post processed shapes PostProcessing(); } return _passNumber >= _maxPass; }
/// <summary> /// Process slide content /// </summary> /// <param name="slideNode">Slide content node</param> /// <returns>true if completed; false if paused</returns> private bool ProcessSlideContent(Node slideNode) { // note: width of slide content area is 648.0 if (slideNode.Children.Count == 0) // ignore empty node return true; Stack<Node> nodes = new Stack<Node>(); // copy content to stack foreach (Node item in slideNode.Children.Reverse<Node>()) { nodes.Push(item); } Node currentNode; PowerPoint.Shape shape = null; // skip expanding of child nodes to stack bool skip; // list of shapes used for reshaper List<PowerPoint.Shape> reshapeShapes = new List<PowerPoint.Shape>(); // process nodes on stack while (nodes.Count != 0) { currentNode = nodes.Pop(); skip = false; if(reshapeShapes.Count > 1) Reshaper(reshapeShapes); // process node depending on its type switch (currentNode.Type) { case "string": if (shape == null) { UpdateBottomShapeBorder(true); _format.Invalidate(); shape = _slide.Shapes.AddTextbox(MsoTextOrientation.msoTextOrientationHorizontal, 36.0f, _bottomShapeBorder + 5.0f, 648.0f, 10.0f); string tmp = currentNode.Content as string; if (Misc.StartsWithNewLine(tmp)) // if new shape starts with new line, then call reshaper on previous shapes { Reshaper(reshapeShapes); reshapeShapes.Clear(); tmp = tmp.TrimStart(); if (tmp.Length == 0) // only empty paragraph -> don't start new shape { shape.Delete(); shape = null; } else { _format.AppendText(shape, tmp); } } else { _format.AppendText(shape, tmp); } reshapeShapes.Add(shape); } else { _format.AppendText(shape, currentNode.Content as string); } break; case "paragraph": if(shape != null && shape.HasTextFrame == MsoTriState.msoTrue && !Misc.EndsWithNewLine(shape.TextFrame2.TextRange.Text)) _format.AppendText(shape, "\r"); // also do reshaping Reshaper(reshapeShapes); reshapeShapes.Clear(); break; case "pause": if (Pause()) return false; break; case "today": if (shape == null) { UpdateBottomShapeBorder(true); _format.Invalidate(); shape = _slide.Shapes.AddTextbox(MsoTextOrientation.msoTextOrientationHorizontal, 36.0f, _bottomShapeBorder + 5.0f, 648.0f, 10.0f); reshapeShapes.Add(shape); } shape.TextFrame.TextRange.InsertDateTime(PowerPoint.PpDateTimeFormat.ppDateTimeFigureOut, MsoTriState.msoTrue); break; case "image": // previous shape ends with new paragraph - image will start on new line if (shape != null && shape.HasTextFrame == MsoTriState.msoTrue && Misc.EndsWithNewLine(shape.TextFrame2.TextRange.Text)) { Reshaper(reshapeShapes); reshapeShapes.Clear(); } UpdateBottomShapeBorder(true); PowerPoint.Shape imageShape; GenerateImage(currentNode, out imageShape); reshapeShapes.Add(imageShape); shape = null; break; case "bulletlist": case "numberedlist": skip = true; Reshaper(reshapeShapes); reshapeShapes.Clear(); UpdateBottomShapeBorder(true); shape = _slide.Shapes.AddTextbox(MsoTextOrientation.msoTextOrientationHorizontal, 36.0f, _bottomShapeBorder + 5.0f, 648.0f, 10.0f); if (!GenerateList(currentNode.Children, shape, 1, currentNode.Type == "bulletlist" ? MsoBulletType.msoBulletUnnumbered : MsoBulletType.msoBulletNumbered)) return false; shape = null; break; case "table": skip = true; // previous shape ends with new paragraph - table will start on new line if (shape != null && shape.HasTextFrame == MsoTriState.msoTrue && Misc.EndsWithNewLine(shape.TextFrame2.TextRange.Text)) { Reshaper(reshapeShapes); reshapeShapes.Clear(); } UpdateBottomShapeBorder(true); PowerPoint.Shape tableShape; if (!GenerateTable(currentNode, out tableShape)) { reshapeShapes.Add(tableShape); // reshape before ending this pass Reshaper(reshapeShapes); return false; // table processing was paused } if (tableShape == null) // table wasn't generated so call reshaper and next shape will start at new "line" { Reshaper(reshapeShapes); reshapeShapes.Clear(); } else { reshapeShapes.Add(tableShape); } shape = null; break; case "descriptionlist": skip = true; Reshaper(reshapeShapes); reshapeShapes.Clear(); UpdateBottomShapeBorder(true); if (!GenerateDescriptionList(currentNode.Children)) return false; shape = null; break; case "titlepage": Reshaper(reshapeShapes); reshapeShapes.Clear(); UpdateBottomShapeBorder(true); GenerateTitlePage(); shape = null; break; default: // other -> check for simple formats SimpleTextFormat(nodes, currentNode); break; } if (currentNode.Children == null || skip) continue; // push child nodes to stack foreach (Node item in currentNode.Children.Reverse<Node>()) { nodes.Push(item); } } // final reshaper call on slide Reshaper(reshapeShapes); return true; }
/// <summary> /// Modify current formatting according to node. /// </summary> /// <param name="node">Node containing new font settings</param> public void ModifyFormat(Node node) { // save current font settings _settingsStack.Push(_currentSettings.Clone() as FormatSettings); _changed = true; switch (node.Type) { case "bold": _currentSettings.Bold = MsoTriState.msoTrue; break; case "italic": _currentSettings.Italic = MsoTriState.msoTrue; break; case "underline": _currentSettings.Underline = MsoTextUnderlineType.msoUnderlineSingleLine; break; case "smallcaps": _currentSettings.Smallcaps = MsoTriState.msoTrue; break; case "typewriter": _currentSettings.FontFamily = @"Courier New"; break; case "color": _currentSettings.Color = ParseColor(node.OptionalParams, node.Content as string); break; // coeficients for relative font size are computed from default font size for each LaTeX size command case "tiny": _currentSettings.FontSize = _baseFontSize / 2f; break; case "scriptsize": _currentSettings.FontSize = _baseFontSize / 1.4285714285714285714285714285714f; break; case "footnotesize": _currentSettings.FontSize = _baseFontSize / 1.25f; break; case "small": _currentSettings.FontSize = _baseFontSize / 1.1111111111111111111111111111111f; break; case "normalsize": _currentSettings.FontSize = _baseFontSize; break; case "large": _currentSettings.FontSize = _baseFontSize * 1.2f; break; case "Large": _currentSettings.FontSize = _baseFontSize * 1.44f; break; case "LARGE": _currentSettings.FontSize = _baseFontSize * 1.728f; break; case "huge": _currentSettings.FontSize = _baseFontSize * 2.0736f; break; case "Huge": _currentSettings.FontSize = _baseFontSize * 2.48832f; break; default: _settingsStack.Pop(); // no changes -> throw away stack top (saved at start of this method) _changed = false; break; } }
/// <summary> /// Build (generate) title part. /// </summary> /// <param name="shape">Prepared title shape</param> /// <param name="content">Title content</param> /// <param name="format">Text format</param> /// <returns>true if completed; false if paused</returns> private bool BuildTitlePart(PowerPoint.Shape shape, List<Node> content, TextFormat format) { if (content.Count == 0) // ignore empty node return true; Stack<Node> nodes = new Stack<Node>(); // copy content to stack foreach (Node item in content.Reverse<Node>()) { nodes.Push(item); } Node currentNode; Node rollbackNode = new Node("__format_pop"); // process nodes on stack while (nodes.Count != 0) { currentNode = nodes.Pop(); // process node depending on its type switch (currentNode.Type) { case "bold": case "italic": case "underline": case "smallcaps": case "typewriter": case "color": case "tiny": case "scriptsize": case "footnotesize": case "small": case "normalsize": case "large": case "Large": case "LARGE": case "huge": case "Huge": // check overlay settings int min = currentNode.OverlayList.Count != 0 ? currentNode.OverlayList.Min() : int.MaxValue; _maxPass = Math.Max(Misc.MaxOverlay(currentNode.OverlayList), _maxPass); // set maximal number of passes from overlay specification if (currentNode.OverlayList.Count == 0 || currentNode.OverlayList.Contains(_passNumber) || min < 0 && Math.Abs(min) < _passNumber) { format.ModifyFormat(currentNode); nodes.Push(rollbackNode); } break; case "__format_pop": // special node -> pop formatting from stack format.RollBackFormat(); break; case "string": format.AppendText(shape, currentNode.Content as string); break; case "paragraph": format.AppendText(shape, "\r"); break; case "today": shape.TextFrame.TextRange.InsertDateTime(PowerPoint.PpDateTimeFormat.ppDateTimeFigureOut, MsoTriState.msoTrue); break; case "pause": _localPauseCounter++; if (_localPauseCounter > _pauseCounter) { if (_passNumber == _maxPass) // increase number of passes _maxPass++; return false; } break; default: // unknown node -> ignore break; } if (currentNode.Children == null) continue; // push child nodes to stack foreach (Node item in currentNode.Children.Reverse<Node>()) { nodes.Push(item); } } return true; }
/// <summary> /// Process slide content /// </summary> /// <param name="slideNode">Slide node</param> private void ProcessSlide(Node slideNode) { // presentation slide PowerPoint.Slide slide; // increment current slide number _currentSlide++; int passNumber = 0, pauseCounter = 0; int maxPass = 0; bool titleNextPass = true, slideNextPass = true, paused = false; SlideBuilder slideBuilder = new SlideBuilder(_preambuleSettings, _currentSlide, _baseFontSize); TitleBuilder titleBuilder = new TitleBuilder(_baseFontSize); // list of sub-slides List<PowerPoint.Slide> subSlides = new List<PowerPoint.Slide>(); // slide settings SlideSettings slideSettings = new SlideSettings(slideNode.OptionalParams); do { // --- loop over all overlays if (paused) { pauseCounter++; paused = false; } passNumber++; _slideIndex++; // create new slide -> if slide contains title, use layout with title if (_frametitleTable.ContainsKey(_currentSlide)) { FrametitleRecord record = _frametitleTable[_currentSlide]; // check if slide has title for current pass if (record.SubtitleOverlaySet.Count == 0 && record.Subtitle != null || record.TitleOverlaySet.Count == 0 && record.Title != null || Misc.ShowOverlay(passNumber, record.SubtitleOverlaySet, ref maxPass) && record.Subtitle != null || Misc.ShowOverlay(passNumber, record.TitleOverlaySet, ref maxPass) && record.Title != null) { slide = _pptPresentation.Slides.Add(_slideIndex, PowerPoint.PpSlideLayout.ppLayoutTitleOnly); subSlides.Add(slide); // add slide to list of sub-slides titleNextPass = titleBuilder.BuildTitle(slide.Shapes.Title, _frametitleTable[_currentSlide], passNumber, pauseCounter, out paused); if (paused) continue; slideNextPass = slideBuilder.BuildSlide(slide, slideNode, passNumber, pauseCounter, out paused); continue; } } slide = _pptPresentation.Slides.Add(_slideIndex, PowerPoint.PpSlideLayout.ppLayoutBlank); subSlides.Add(slide); // add slide to list of sub-slides slideNextPass = slideBuilder.BuildSlide(slide, slideNode, passNumber, pauseCounter, out paused); } while (!titleNextPass || !slideNextPass); // --- end loop over all overlays // change slide content vertical align if (slideSettings.ContentAlign != SlideSettings.Align.TOP) { float bottom = float.MinValue; // compute maximal top and bottom of slide content foreach (PowerPoint.Slide subSlide in subSlides) { bottom = Math.Max(bottom, subSlide.Shapes[subSlide.Shapes.Count].Top + subSlide.Shapes[subSlide.Shapes.Count].Height); } float change; if (slideSettings.ContentAlign == SlideSettings.Align.BOTTOM) change = (540.0f - bottom) - 10.0f; else change = (540.0f - bottom) / 2.5f; foreach (PowerPoint.Slide subSlide in subSlides) { foreach (PowerPoint.Shape shape in subSlide.Shapes) { if (subSlide.Shapes.HasTitle != MsoTriState.msoTrue || shape != subSlide.Shapes.Title) { shape.Top = shape.Top + change; } } } } // report progress RaiseProgress(); }
/// <summary> /// Process document preambule part /// </summary> /// <param name="preambule">Preambule node</param> /// <param name="documentclassOptionals">\document class optional parameters (size)</param> private void ProcessPreambule(Node preambule, string documentclassOptionals) { // parse preambule _preambuleSettings.Parse(preambule); // process \documentclass optional parameters string[] parts = documentclassOptionals.Split(new char[]{','}, StringSplitOptions.RemoveEmptyEntries); foreach (string part in parts) { float size = Misc.ParseLength(part); if (size > 0) _baseFontSize = Settings.Instance.AdjustSize ? size / 2 : size; } }
/// <summary> /// Process document body part /// </summary> /// <param name="body">Body node</param> private void ProcessBody(Node body) { foreach (Node node in body.Children) { switch (node.Type) { // title page settings case "author": case "title": case "institute": case "date": _preambuleSettings.TitlepageSettings[node.Type] = node.Children; break; // slide case "slide": ProcessSlide(node); break; // unknown or invalid node -> ignore default: break; } } }
/// <summary> /// Build presentation from document tree. /// </summary> /// <param name="filename">Filename of input file (used for output file)</param> /// <param name="outputPath">Output directory</param> /// <param name="document">Document tree</param> /// <param name="slideCount">Number of slides in document tree</param> /// <param name="sectionTable">Table of document sections</param> /// <param name="frametitleTable">Table of frame titles</param> public void Build(string filename, string outputPath, Node document, int slideCount, List<SectionRecord> sectionTable, Dictionary<int, FrametitleRecord> frametitleTable) { #region Initialize internal variables if (outputPath.Length == 0) { _filename = Path.Combine(Directory.GetCurrentDirectory(), "output", Path.GetFileNameWithoutExtension(filename)); try { // if output directory doesn't exist then create it if (!Directory.Exists(Path.Combine(Directory.GetCurrentDirectory(), "output"))) Directory.CreateDirectory(Path.Combine(Directory.GetCurrentDirectory(), "output")); } catch (Exception) { throw new PowerPointApplicationException("Couldn't create default output directory."); } } else { _filename = Path.Combine(outputPath, Path.GetFileNameWithoutExtension(filename)); try { // if output directory doesn't exist then create it if (!Directory.Exists(outputPath)) Directory.CreateDirectory(outputPath); } catch (Exception) { throw new PowerPointApplicationException("Couldn't create output directory."); } } _document = document; _slideCount = slideCount; _sectionTable = sectionTable ?? new List<SectionRecord>(); _frametitleTable = frametitleTable ?? new Dictionary<int, FrametitleRecord>(); _preambuleSettings = new PreambuleSettings(Path.GetDirectoryName(filename)); _currentSlide = 0; _slideIndex = 0; _currentProgress = BasicProgress; #endregion // Initialize internal variables Node preambule = _document.FindFirstNode("preambule"); if (preambule == null) throw new DocumentBuilderException("Couldn't build document, something went wrong. Please try again."); ProcessPreambule(preambule, _document.OptionalParams); // create new presentation without window _pptPresentation = _pptApplication.Presentations.Add(MsoTriState.msoFalse); Node body = _document.FindFirstNode("body"); if (body == null) throw new DocumentBuilderException("Couldn't build document, something went wrong. Please try again."); ProcessBody(body); try { _pptPresentation.SaveAs(_filename, Settings.Instance.SaveAs); } catch (Exception) { throw new DocumentBuilderException("Couldn't save output file."); } finally { // final progress change after saving RaiseProgress(); } // print save message switch (Settings.Instance.SaveAs) { case Microsoft.Office.Interop.PowerPoint.PpSaveAsFileType.ppSaveAsDefault: case Microsoft.Office.Interop.PowerPoint.PpSaveAsFileType.ppSaveAsOpenXMLPresentation: case Microsoft.Office.Interop.PowerPoint.PpSaveAsFileType.ppSaveAsPresentation: Messenger.Instance.SendMessage(System.IO.Path.GetFileName(filename) + " - Output saved to: \"" + _pptPresentation.FullName + "\""); break; case Microsoft.Office.Interop.PowerPoint.PpSaveAsFileType.ppSaveAsPDF: Messenger.Instance.SendMessage(System.IO.Path.GetFileName(filename) + " - Output saved to: \"" + _filename + ".pdf\""); break; default: Messenger.Instance.SendMessage(System.IO.Path.GetFileName(filename) + " - Output saved to output directory."); break; } try { _pptPresentation.Close(); _pptPresentation = null; } catch { } }