400 lines
14 KiB
C#
400 lines
14 KiB
C#
#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
|
||
{
|
||
/// <summary>
|
||
/// 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.
|
||
/// </summary>
|
||
public class XTextFormatter
|
||
{
|
||
/// <summary>
|
||
/// Initializes a new instance of the <see cref="XTextFormatter"/> class.
|
||
/// </summary>
|
||
public XTextFormatter(XGraphics gfx)
|
||
{
|
||
if (gfx == null)
|
||
throw new ArgumentNullException("gfx");
|
||
_gfx = gfx;
|
||
}
|
||
readonly XGraphics _gfx;
|
||
|
||
/// <summary>
|
||
/// Gets or sets the text.
|
||
/// </summary>
|
||
/// <value>The text.</value>
|
||
public string Text
|
||
{
|
||
get { return _text; }
|
||
set { _text = value; }
|
||
}
|
||
string _text;
|
||
|
||
/// <summary>
|
||
/// Gets or sets the font.
|
||
/// </summary>
|
||
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;
|
||
|
||
/// <summary>
|
||
/// Gets or sets the bounding box of the layout.
|
||
/// </summary>
|
||
public XRect LayoutRectangle
|
||
{
|
||
get { return _layoutRectangle; }
|
||
set { _layoutRectangle = value; }
|
||
}
|
||
XRect _layoutRectangle;
|
||
|
||
/// <summary>
|
||
/// Gets or sets the alignment of the text.
|
||
/// </summary>
|
||
public XParagraphAlignment Alignment
|
||
{
|
||
get { return _alignment; }
|
||
set { _alignment = value; }
|
||
}
|
||
XParagraphAlignment _alignment = XParagraphAlignment.Left;
|
||
|
||
/// <summary>
|
||
/// Draws the text.
|
||
/// </summary>
|
||
/// <param name="text">The text to be drawn.</param>
|
||
/// <param name="font">The font.</param>
|
||
/// <param name="brush">The text brush.</param>
|
||
/// <param name="layoutRectangle">The layout rectangle.</param>
|
||
public void DrawString(string text, XFont font, XBrush brush, XRect layoutRectangle)
|
||
{
|
||
DrawString(text, font, brush, layoutRectangle, XStringFormats.TopLeft);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Draws the text.
|
||
/// </summary>
|
||
/// <param name="text">The text to be drawn.</param>
|
||
/// <param name="font">The font.</param>
|
||
/// <param name="brush">The text brush.</param>
|
||
/// <param name="layoutRectangle">The layout rectangle.</param>
|
||
/// <param name="format">The format. Must be <c>XStringFormat.TopLeft</c></param>
|
||
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);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Align center, right, or justify.
|
||
/// </summary>
|
||
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<Block> _blocks = new List<Block>();
|
||
|
||
enum BlockType
|
||
{
|
||
Text, Space, Hyphen, LineBreak,
|
||
}
|
||
|
||
/// <summary>
|
||
/// Represents a single word.
|
||
/// </summary>
|
||
class Block
|
||
{
|
||
/// <summary>
|
||
/// Initializes a new instance of the <see cref="Block"/> class.
|
||
/// </summary>
|
||
/// <param name="text">The text of the block.</param>
|
||
/// <param name="type">The type of the block.</param>
|
||
/// <param name="width">The width of the text.</param>
|
||
public Block(string text, BlockType type, double width)
|
||
{
|
||
Text = text;
|
||
Type = type;
|
||
Width = width;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Initializes a new instance of the <see cref="Block"/> class.
|
||
/// </summary>
|
||
/// <param name="type">The type.</param>
|
||
public Block(BlockType type)
|
||
{
|
||
Type = type;
|
||
}
|
||
|
||
/// <summary>
|
||
/// The text represented by this block.
|
||
/// </summary>
|
||
public readonly string Text;
|
||
|
||
/// <summary>
|
||
/// The type of the block.
|
||
/// </summary>
|
||
public readonly BlockType Type;
|
||
|
||
/// <summary>
|
||
/// The width of the text.
|
||
/// </summary>
|
||
public readonly double Width;
|
||
|
||
/// <summary>
|
||
/// The location relative to the upper left corner of the layout rectangle.
|
||
/// </summary>
|
||
public XPoint Location;
|
||
|
||
/// <summary>
|
||
/// The alignment of this line.
|
||
/// </summary>
|
||
public XParagraphAlignment Alignment;
|
||
|
||
/// <summary>
|
||
/// A flag indicating that this is the last block that fits in the layout rectangle.
|
||
/// </summary>
|
||
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
|
||
// - ...
|
||
}
|
||
}
|