#region PDFsharp - A .NET library for processing PDF // // Authors: // Stefan Lange // // Copyright (c) 2005-2017 empira Software GmbH, Cologne Area (Germany) // // http://www.pdfsharp.com // http://sourceforge.net/projects/pdfsharp // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software"), // to deal in the Software without restriction, including without limitation // the rights to use, copy, modify, merge, publish, distribute, sublicense, // and/or sell copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included // in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. #endregion using System; using System.Collections.Generic; using PdfSharp.Pdf.IO; namespace PdfSharp.Drawing.Layout { /// /// Represents a very simple text formatter. /// If this class does not satisfy your needs on formatting paragraphs I recommend to take a look /// at MigraDoc Foundation. Alternatively you should copy this class in your own source code and modify it. /// public class XTextFormatter { /// /// Initializes a new instance of the class. /// public XTextFormatter(XGraphics gfx) { if (gfx == null) throw new ArgumentNullException("gfx"); _gfx = gfx; } readonly XGraphics _gfx; /// /// Gets or sets the text. /// /// The text. public string Text { get { return _text; } set { _text = value; } } string _text; /// /// Gets or sets the font. /// public XFont Font { get { return _font; } set { if (value == null) throw new ArgumentNullException("Font"); _font = value; _lineSpace = _font.GetHeight(); // old: _font.GetHeight(_gfx); _cyAscent = _lineSpace * _font.CellAscent / _font.CellSpace; _cyDescent = _lineSpace * _font.CellDescent / _font.CellSpace; // HACK in XTextFormatter _spaceWidth = _gfx.MeasureString("x x", value).Width; _spaceWidth -= _gfx.MeasureString("xx", value).Width; } } XFont _font; double _lineSpace; double _cyAscent; double _cyDescent; double _spaceWidth; /// /// Gets or sets the bounding box of the layout. /// public XRect LayoutRectangle { get { return _layoutRectangle; } set { _layoutRectangle = value; } } XRect _layoutRectangle; /// /// Gets or sets the alignment of the text. /// public XParagraphAlignment Alignment { get { return _alignment; } set { _alignment = value; } } XParagraphAlignment _alignment = XParagraphAlignment.Left; /// /// Draws the text. /// /// The text to be drawn. /// The font. /// The text brush. /// The layout rectangle. public void DrawString(string text, XFont font, XBrush brush, XRect layoutRectangle) { DrawString(text, font, brush, layoutRectangle, XStringFormats.TopLeft); } /// /// Draws the text. /// /// The text to be drawn. /// The font. /// The text brush. /// The layout rectangle. /// The format. Must be XStringFormat.TopLeft public void DrawString(string text, XFont font, XBrush brush, XRect layoutRectangle, XStringFormat format) { if (text == null) throw new ArgumentNullException("text"); if (font == null) throw new ArgumentNullException("font"); if (brush == null) throw new ArgumentNullException("brush"); if (format.Alignment != XStringAlignment.Near || format.LineAlignment != XLineAlignment.Near) throw new ArgumentException("Only TopLeft alignment is currently implemented."); Text = text; Font = font; LayoutRectangle = layoutRectangle; if (text.Length == 0) return; CreateBlocks(); CreateLayout(); double dx = layoutRectangle.Location.X; double dy = layoutRectangle.Location.Y + _cyAscent; int count = _blocks.Count; for (int idx = 0; idx < count; idx++) { Block block = _blocks[idx]; if (block.Stop) break; if (block.Type == BlockType.LineBreak) continue; _gfx.DrawString(block.Text, font, brush, dx + block.Location.X, dy + block.Location.Y); } } void CreateBlocks() { _blocks.Clear(); int length = _text.Length; bool inNonWhiteSpace = false; int startIndex = 0, blockLength = 0; for (int idx = 0; idx < length; idx++) { char ch = _text[idx]; // Treat CR and CRLF as LF if (ch == Chars.CR) { if (idx < length - 1 && _text[idx + 1] == Chars.LF) idx++; ch = Chars.LF; } if (ch == Chars.LF) { if (blockLength != 0) { string token = _text.Substring(startIndex, blockLength); _blocks.Add(new Block(token, BlockType.Text, _gfx.MeasureString(token, _font).Width)); } startIndex = idx + 1; blockLength = 0; _blocks.Add(new Block(BlockType.LineBreak)); } // The non-breaking space is whitespace, so we treat it like non-whitespace. else if (ch != Chars.NonBreakableSpace && char.IsWhiteSpace(ch)) { if (inNonWhiteSpace) { string token = _text.Substring(startIndex, blockLength); _blocks.Add(new Block(token, BlockType.Text, _gfx.MeasureString(token, _font).Width)); startIndex = idx + 1; blockLength = 0; } else { blockLength++; } } else { inNonWhiteSpace = true; blockLength++; } } if (blockLength != 0) { string token = _text.Substring(startIndex, blockLength); _blocks.Add(new Block(token, BlockType.Text, _gfx.MeasureString(token, _font).Width)); } } void CreateLayout() { double rectWidth = _layoutRectangle.Width; double rectHeight = _layoutRectangle.Height - _cyAscent - _cyDescent; int firstIndex = 0; double x = 0, y = 0; int count = _blocks.Count; for (int idx = 0; idx < count; idx++) { Block block = _blocks[idx]; if (block.Type == BlockType.LineBreak) { if (Alignment == XParagraphAlignment.Justify) _blocks[firstIndex].Alignment = XParagraphAlignment.Left; AlignLine(firstIndex, idx - 1, rectWidth); firstIndex = idx + 1; x = 0; y += _lineSpace; if (y > rectHeight) { block.Stop = true; break; } } else { double width = block.Width; if ((x + width <= rectWidth || x == 0) && block.Type != BlockType.LineBreak) { block.Location = new XPoint(x, y); x += width + _spaceWidth; } else { AlignLine(firstIndex, idx - 1, rectWidth); firstIndex = idx; y += _lineSpace; if (y > rectHeight) { block.Stop = true; break; } block.Location = new XPoint(0, y); x = width + _spaceWidth; } } } if (firstIndex < count && Alignment != XParagraphAlignment.Justify) AlignLine(firstIndex, count - 1, rectWidth); } /// /// Align center, right, or justify. /// void AlignLine(int firstIndex, int lastIndex, double layoutWidth) { XParagraphAlignment blockAlignment = _blocks[firstIndex].Alignment; if (_alignment == XParagraphAlignment.Left || blockAlignment == XParagraphAlignment.Left) return; int count = lastIndex - firstIndex + 1; if (count == 0) return; double totalWidth = -_spaceWidth; for (int idx = firstIndex; idx <= lastIndex; idx++) totalWidth += _blocks[idx].Width + _spaceWidth; double dx = Math.Max(layoutWidth - totalWidth, 0); //Debug.Assert(dx >= 0); if (_alignment != XParagraphAlignment.Justify) { if (_alignment == XParagraphAlignment.Center) dx /= 2; for (int idx = firstIndex; idx <= lastIndex; idx++) { Block block = _blocks[idx]; block.Location += new XSize(dx, 0); } } else if (count > 1) // case: justify { dx /= count - 1; for (int idx = firstIndex + 1, i = 1; idx <= lastIndex; idx++, i++) { Block block = _blocks[idx]; block.Location += new XSize(dx * i, 0); } } } readonly List _blocks = new List(); enum BlockType { Text, Space, Hyphen, LineBreak, } /// /// Represents a single word. /// class Block { /// /// Initializes a new instance of the class. /// /// The text of the block. /// The type of the block. /// The width of the text. public Block(string text, BlockType type, double width) { Text = text; Type = type; Width = width; } /// /// Initializes a new instance of the class. /// /// The type. public Block(BlockType type) { Type = type; } /// /// The text represented by this block. /// public readonly string Text; /// /// The type of the block. /// public readonly BlockType Type; /// /// The width of the text. /// public readonly double Width; /// /// The location relative to the upper left corner of the layout rectangle. /// public XPoint Location; /// /// The alignment of this line. /// public XParagraphAlignment Alignment; /// /// A flag indicating that this is the last block that fits in the layout rectangle. /// public bool Stop; } // TODO: // - more XStringFormat variations // - calculate bounding box // - left and right indent // - first line indent // - margins and paddings // - background color // - text background color // - border style // - hyphens, soft hyphens, hyphenation // - kerning // - change font, size, text color etc. // - line spacing // - underline and strike-out variation // - super- and sub-script // - ... } }