git-svn-id: https://pdfsharp.svn.codeplex.com/svn@39620 56d0cb2f-6006-4f69-a5a2-94740...
[pdfsharp:pdfsharp.git] / PDFsharp / code / PdfSharp / PdfSharp.Drawing.Layout / XTextFormatter.cs
1 #region PDFsharp - A .NET library for processing PDF\r
2 //\r
3 // Authors:\r
4 //   Stefan Lange (mailto:Stefan.Lange@pdfsharp.com)\r
5 //\r
6 // Copyright (c) 2005-2009 empira Software GmbH, Cologne (Germany)\r
7 //\r
8 // http://www.pdfsharp.com\r
9 // http://sourceforge.net/projects/pdfsharp\r
10 //\r
11 // Permission is hereby granted, free of charge, to any person obtaining a\r
12 // copy of this software and associated documentation files (the "Software"),\r
13 // to deal in the Software without restriction, including without limitation\r
14 // the rights to use, copy, modify, merge, publish, distribute, sublicense,\r
15 // and/or sell copies of the Software, and to permit persons to whom the\r
16 // Software is furnished to do so, subject to the following conditions:\r
17 //\r
18 // The above copyright notice and this permission notice shall be included\r
19 // in all copies or substantial portions of the Software.\r
20 //\r
21 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r
22 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r
23 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\r
24 // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r
25 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\r
26 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER \r
27 // DEALINGS IN THE SOFTWARE.\r
28 #endregion\r
29 \r
30 using System;\r
31 using System.Collections.Generic;\r
32 using System.Diagnostics;\r
33 using System.Collections;\r
34 using System.Text;\r
35 using PdfSharp.Drawing;\r
36 using PdfSharp.Pdf.IO;\r
37 \r
38 namespace PdfSharp.Drawing.Layout\r
39 {\r
40   /// <summary>\r
41   /// Represents a very simple text formatter.\r
42   /// If this class does not satisfy your needs on formatting paragraphs I recommend to take a look\r
43   /// at MigraDoc Foundation. Alternatively you should copy this class in your own source code and modify it.\r
44   /// </summary>\r
45   public class XTextFormatter\r
46   {\r
47     /// <summary>\r
48     /// Initializes a new instance of the <see cref="XTextFormatter"/> class.\r
49     /// </summary>\r
50     public XTextFormatter(XGraphics gfx)\r
51     {\r
52       if (gfx == null)\r
53         throw new ArgumentNullException("gfx");\r
54       this.gfx = gfx;\r
55     }\r
56     XGraphics gfx;\r
57 \r
58     /// <summary>\r
59     /// Gets or sets the text.\r
60     /// </summary>\r
61     /// <value>The text.</value>\r
62     public string Text\r
63     {\r
64       get { return this.text; }\r
65       set { this.text = value; }\r
66     }\r
67     string text;\r
68 \r
69     /// <summary>\r
70     /// Gets or sets the font.\r
71     /// </summary>\r
72     public XFont Font\r
73     {\r
74       get { return this.font; }\r
75       set \r
76       {\r
77         if (value == null)\r
78           throw new ArgumentNullException("font");\r
79         this.font = value;\r
80 \r
81         this.lineSpace = font.GetHeight(this.gfx);\r
82         this.cyAscent = lineSpace * font.cellAscent / font.cellSpace;\r
83         this.cyDescent = lineSpace * font.cellDescent / font.cellSpace;\r
84 \r
85         // HACK in XTextFormatter\r
86         this.spaceWidth = gfx.MeasureString("x x", value).width;\r
87         this.spaceWidth -= gfx.MeasureString("xx", value).width;\r
88       }\r
89     }\r
90     XFont font;\r
91     double lineSpace;\r
92     double cyAscent;\r
93     double cyDescent;\r
94     double spaceWidth;\r
95 \r
96     /// <summary>\r
97     /// Gets or sets the bounding box of the layout.\r
98     /// </summary>\r
99     public XRect LayoutRectangle\r
100     {\r
101       get { return this.layoutRectangle; }\r
102       set { this.layoutRectangle = value; }\r
103     }\r
104     XRect layoutRectangle;\r
105 \r
106     /// <summary>\r
107     /// Gets or sets the alignment of the text.\r
108     /// </summary>\r
109     public XParagraphAlignment Alignment\r
110     {\r
111       get { return this.alignment; }\r
112       set { this.alignment = value; }\r
113     }\r
114     XParagraphAlignment alignment = XParagraphAlignment.Left;\r
115 \r
116     /// <summary>\r
117     /// Draws the text.\r
118     /// </summary>\r
119     /// <param name="text">The text to be drawn.</param>\r
120     /// <param name="font">The font.</param>\r
121     /// <param name="brush">The text brush.</param>\r
122     /// <param name="layoutRectangle">The layout rectangle.</param>\r
123     public void DrawString(string text, XFont font, XBrush brush, XRect layoutRectangle)\r
124     {\r
125       DrawString(text, font, brush, layoutRectangle, XStringFormats.TopLeft);\r
126     }\r
127 \r
128     /// <summary>\r
129     /// Draws the text.\r
130     /// </summary>\r
131     /// <param name="text">The text to be drawn.</param>\r
132     /// <param name="font">The font.</param>\r
133     /// <param name="brush">The text brush.</param>\r
134     /// <param name="layoutRectangle">The layout rectangle.</param>\r
135     /// <param name="format">The format. Must be <c>XStringFormat.TopLeft</c></param>\r
136     public void DrawString(string text, XFont font, XBrush brush, XRect layoutRectangle, XStringFormat format)\r
137     {\r
138       if (text == null)\r
139         throw new ArgumentNullException("text");\r
140       if (font == null)\r
141         throw new ArgumentNullException("font");\r
142       if (brush == null)\r
143         throw new ArgumentNullException("brush");\r
144       if (format.Alignment != XStringAlignment.Near || format.LineAlignment!= XLineAlignment.Near)\r
145         throw new ArgumentException("Only TopLeft alignment is currently implemented.");\r
146 \r
147       Text = text;\r
148       Font = font;\r
149       LayoutRectangle = layoutRectangle;\r
150 \r
151       if (text.Length == 0)\r
152         return;\r
153 \r
154       CreateBlocks();\r
155       \r
156       CreateLayout();\r
157 \r
158       double dx = layoutRectangle.Location.x;\r
159       double dy = layoutRectangle.Location.y + cyAscent;\r
160       int count = this.blocks.Count;\r
161       for (int idx = 0; idx < count; idx++)\r
162       {\r
163         Block block = (Block)this.blocks[idx];\r
164         if (block.Stop)\r
165           break;\r
166         if (block.Type == BlockType.LineBreak)\r
167           continue;\r
168         gfx.DrawString(block.Text, font, brush, dx + block.Location.x, dy + block.Location.y);\r
169       }\r
170     }\r
171 \r
172     void CreateBlocks()\r
173     {\r
174       this.blocks.Clear();\r
175       int length = this.text.Length;\r
176       bool inNonWhiteSpace = false;\r
177       int startIndex = 0, blockLength = 0;\r
178       for (int idx = 0; idx < length; idx++)\r
179       {\r
180         char ch = text[idx];\r
181 \r
182         // Treat CR and CRLF as LF\r
183         if (ch == Chars.CR)\r
184         {\r
185           if (idx < length - 1 && text[idx + 1] == Chars.LF)\r
186             idx++;\r
187           ch = Chars.LF;\r
188         }\r
189         if (ch == Chars.LF)\r
190         {\r
191           if (blockLength != 0)\r
192           {\r
193             string token = text.Substring(startIndex, blockLength);\r
194             this.blocks.Add(new Block(token, BlockType.Text,\r
195               this.gfx.MeasureString(token, this.font).Width));\r
196           }\r
197           startIndex = idx + 1;\r
198           blockLength = 0;\r
199           this.blocks.Add(new Block(BlockType.LineBreak));\r
200         }\r
201         else if (Char.IsWhiteSpace(ch))\r
202         {\r
203           if (inNonWhiteSpace)\r
204           {\r
205             string token = text.Substring(startIndex, blockLength);\r
206             this.blocks.Add(new Block(token, BlockType.Text,\r
207               this.gfx.MeasureString(token, this.font).Width));\r
208             startIndex = idx + 1;\r
209             blockLength = 0;\r
210           }\r
211           else\r
212           {\r
213             blockLength++;\r
214           }\r
215         }\r
216         else\r
217         {\r
218           inNonWhiteSpace = true;\r
219           blockLength++;\r
220         }\r
221       }\r
222       if (blockLength != 0)\r
223       {\r
224         string token = text.Substring(startIndex, blockLength);\r
225         this.blocks.Add(new Block(token, BlockType.Text,\r
226           this.gfx.MeasureString(token, this.font).Width));\r
227       }\r
228     }\r
229 \r
230     void CreateLayout()\r
231     {\r
232       double rectWidth = this.layoutRectangle.width;\r
233       double rectHeight = this.layoutRectangle.height - this.cyAscent - this.cyDescent;\r
234       int firstIndex = 0;\r
235       double x = 0, y = 0;\r
236       int count = this.blocks.Count;\r
237       for (int idx = 0; idx < count; idx++)\r
238       {\r
239         Block block = (Block)this.blocks[idx];\r
240         if (block.Type == BlockType.LineBreak)\r
241         {\r
242           if (Alignment == XParagraphAlignment.Justify)\r
243             ((Block)this.blocks[firstIndex]).Alignment = XParagraphAlignment.Left;\r
244           AlignLine(firstIndex, idx - 1, rectWidth);\r
245           firstIndex = idx + 1;\r
246           x = 0;\r
247           y += this.lineSpace;\r
248         }\r
249         else\r
250         {\r
251           double width = this.spaceWidth + block.Width;\r
252           if ((x + width <= rectWidth || x == 0) && block.Type != BlockType.LineBreak)\r
253           {\r
254             block.Location = new XPoint(x, y);\r
255             x += width;\r
256           }\r
257           else\r
258           {\r
259             AlignLine(firstIndex, idx - 1, rectWidth);\r
260             firstIndex = idx;\r
261             y += this.lineSpace;\r
262             if (y > rectHeight)\r
263             {\r
264               block.Stop = true;\r
265               break;\r
266             }\r
267             block.Location = new XPoint(0, y);\r
268             x = width;\r
269           }\r
270         }\r
271       }\r
272       if (firstIndex < count && Alignment != XParagraphAlignment.Justify)\r
273         AlignLine(firstIndex, count - 1, rectWidth);\r
274     }\r
275 \r
276     /// <summary>\r
277     /// Align center, right or justify.\r
278     /// </summary>\r
279     void AlignLine(int firstIndex, int lastIndex, double layoutWidth)\r
280     {\r
281       XParagraphAlignment blockAlignment = ((Block)(this.blocks[firstIndex])).Alignment;\r
282       if (this.alignment == XParagraphAlignment.Left || blockAlignment == XParagraphAlignment.Left)\r
283         return;\r
284 \r
285       int count = lastIndex - firstIndex + 1;\r
286       if (count == 0)\r
287         return;\r
288 \r
289       double totalWidth = -this.spaceWidth;\r
290       for (int idx = firstIndex; idx <= lastIndex; idx++)\r
291         totalWidth += ((Block)(this.blocks[idx])).Width + this.spaceWidth;\r
292 \r
293       double dx = Math.Max(layoutWidth - totalWidth, 0);\r
294       //Debug.Assert(dx >= 0);\r
295       if (this.alignment != XParagraphAlignment.Justify)\r
296       {\r
297         if (this.alignment == XParagraphAlignment.Center)\r
298           dx /= 2;\r
299         for (int idx = firstIndex; idx <= lastIndex; idx++)\r
300         {\r
301           Block block = (Block)this.blocks[idx];\r
302           block.Location += new XSize(dx, 0);\r
303         }\r
304       }\r
305       else if (count > 1) // case: justify\r
306       {\r
307         dx /= count - 1;\r
308         for (int idx = firstIndex + 1, i = 1; idx <= lastIndex; idx++, i++)\r
309         {\r
310           Block block = (Block)this.blocks[idx];\r
311           block.Location += new XSize(dx * i, 0);\r
312         }\r
313       }\r
314     }\r
315 \r
316     readonly List<Block> blocks = new List<Block>();\r
317 \r
318     enum BlockType\r
319     {\r
320       Text, Space, Hyphen, LineBreak,\r
321     }\r
322 \r
323     /// <summary>\r
324     /// Represents a single word.\r
325     /// </summary>\r
326     class Block\r
327     {\r
328       /// <summary>\r
329       /// Initializes a new instance of the <see cref="Block"/> class.\r
330       /// </summary>\r
331       /// <param name="text">The text of the block.</param>\r
332       /// <param name="type">The type of the block.</param>\r
333       /// <param name="width">The width of the text.</param>\r
334       public Block(string text, BlockType type, double width)\r
335       {\r
336         Text = text;\r
337         Type = type;\r
338         Width = width;\r
339       }\r
340 \r
341       /// <summary>\r
342       /// Initializes a new instance of the <see cref="Block"/> class.\r
343       /// </summary>\r
344       /// <param name="type">The type.</param>\r
345       public Block(BlockType type)\r
346       {\r
347         Type = type;\r
348       }\r
349 \r
350       /// <summary>\r
351       /// The text represented by this block.\r
352       /// </summary>\r
353       public string Text;\r
354 \r
355       /// <summary>\r
356       /// The type of the block.\r
357       /// </summary>\r
358       public BlockType Type;\r
359 \r
360       /// <summary>\r
361       /// The width of the text.\r
362       /// </summary>\r
363       public double Width;\r
364 \r
365       /// <summary>\r
366       /// The location relative to the upper left corner of the layout rectangle.\r
367       /// </summary>\r
368       public XPoint Location;\r
369 \r
370       /// <summary>\r
371       /// The alignment of this line.\r
372       /// </summary>\r
373       public XParagraphAlignment Alignment;\r
374 \r
375       /// <summary>\r
376       /// A flag indicating that this is the last bock that fits in the layout rectangle.\r
377       /// </summary>\r
378       public bool Stop;\r
379     }\r
380     // TODO:\r
381     // - more XStringFormat variations\r
382     // - calculate bounding box\r
383     // - left and right indent\r
384     // - first line indent\r
385     // - margins and paddings\r
386     // - background color\r
387     // - text background color\r
388     // - border style\r
389     // - hyphens, soft hyphens, hyphenation\r
390     // - kerning\r
391     // - change font, size, text color etc.\r
392     // - line spacing\r
393     // - underine and strike-out variation\r
394     // - super- and sub-script\r
395     // - ...\r
396   }\r
397 }\r