1/*
2 * Copyright 2013, Stephan Aßmus <superstippi@gmx.de>.
3 * All rights reserved. Distributed under the terms of the MIT License.
4 */
5
6#include "MarkupParser.h"
7
8#include <new>
9
10#include <math.h>
11
12#include <utf8_functions.h>
13
14
15MarkupParser::MarkupParser()
16	:
17	fNormalStyle(),
18	fBoldStyle(),
19	fItalicStyle(),
20	fBoldItalicStyle(),
21	fHeadingStyle(),
22
23	fParagraphStyle(),
24	fHeadingParagraphStyle(),
25	fBulletStyle(),
26
27	fCurrentCharacterStyle(&fNormalStyle),
28	fCurrentParagraphStyle(&fParagraphStyle),
29	fSpanStartOffset(0)
30{
31	_InitStyles();
32}
33
34
35MarkupParser::MarkupParser(const CharacterStyle& characterStyle,
36	const ParagraphStyle& paragraphStyle)
37	:
38	fNormalStyle(characterStyle),
39	fBoldStyle(),
40	fItalicStyle(),
41	fBoldItalicStyle(),
42	fHeadingStyle(),
43
44	fParagraphStyle(paragraphStyle),
45	fHeadingParagraphStyle(),
46	fBulletStyle(),
47
48	fCurrentCharacterStyle(&fNormalStyle),
49	fCurrentParagraphStyle(&fParagraphStyle),
50	fSpanStartOffset(0)
51{
52	_InitStyles();
53}
54
55
56void
57MarkupParser::SetStyles(const CharacterStyle& characterStyle,
58	const ParagraphStyle& paragraphStyle)
59{
60	fNormalStyle = characterStyle;
61	fParagraphStyle = paragraphStyle;
62	_InitStyles();
63}
64
65
66TextDocumentRef
67MarkupParser::CreateDocumentFromMarkup(const BString& text)
68{
69	TextDocumentRef document(new(std::nothrow) TextDocument(), true);
70	if (document.Get() == NULL)
71		return document;
72
73	AppendMarkup(document, text);
74
75	return document;
76}
77
78
79void
80MarkupParser::AppendMarkup(const TextDocumentRef& document, const BString& text)
81{
82	fTextDocument.SetTo(document);
83
84	fCurrentCharacterStyle = &fNormalStyle;
85	fCurrentParagraphStyle = &fParagraphStyle;
86
87	fCurrentParagraph = Paragraph(*fCurrentParagraphStyle);
88	fSpanStartOffset = 0;
89
90	_ParseText(text);
91
92	fTextDocument.Unset();
93}
94
95
96// #pragma mark - private
97
98
99void
100MarkupParser::_InitStyles()
101{
102	fBoldStyle = fNormalStyle;
103	fBoldStyle.SetBold(true);
104
105	fItalicStyle = fNormalStyle;
106	fItalicStyle.SetItalic(true);
107
108	fBoldItalicStyle = fNormalStyle;
109	fBoldItalicStyle.SetBold(true);
110	fBoldItalicStyle.SetItalic(true);
111
112	float fontSize = fNormalStyle.Font().Size();
113
114	fHeadingStyle = fNormalStyle;
115	fHeadingStyle.SetFontSize(ceilf(fontSize * 1.15f));
116	fHeadingStyle.SetBold(true);
117
118	fHeadingParagraphStyle = fParagraphStyle;
119	fHeadingParagraphStyle.SetSpacingTop(ceilf(fontSize * 0.8f));
120	fHeadingParagraphStyle.SetSpacingBottom(ceilf(fontSize * 0.5f));
121	fHeadingParagraphStyle.SetJustify(false);
122
123	fBulletStyle = fParagraphStyle;
124	fBulletStyle.SetBullet(Bullet("•", fontSize));
125	fBulletStyle.SetLineInset(ceilf(fontSize * 0.8f));
126}
127
128
129void
130MarkupParser::_ParseText(const BString& text)
131{
132	int32 start = 0;
133	int32 offset = 0;
134
135	int32 charCount = text.CountChars();
136	const char* c = text.String();
137
138	while (offset <= charCount) {
139		uint32 nextChar = UTF8ToCharCode(&c);
140
141		switch (nextChar) {
142// Requires two line-breaks to start a new paragraph, unles the current
143// paragraph is already considered a bullet list item. Doesn't work well
144// with current set of packages.
145//			case '\n':
146//				_CopySpan(text, start, offset);
147//				if (offset + 1 < charCount && c[0] == '\n') {
148//					_FinishParagraph();
149//					offset += 1;
150//					c += 1;
151//				} else if (fCurrentParagraph.Style() == fBulletStyle) {
152//					_FinishParagraph();
153//				}
154//				start = offset + 1;
155//				break;
156
157			case '\n':
158				_CopySpan(text, start, offset);
159				if (offset > 0 && c[-1] != ' ')
160					_FinishParagraph(offset >= charCount);
161				start = offset + 1;
162				break;
163
164			case '\0':
165				_CopySpan(text, start, offset);
166				_FinishParagraph(true);
167				start = offset + 1;
168				break;
169
170			case '\'':
171				if (offset + 2 < charCount && c[0] == '\'') {
172					int32 tickCount = 2;
173					if (c[1] == '\'')
174						tickCount = 3;
175
176					// Copy previous span using current style, excluding the
177					// ticks.
178					_CopySpan(text, start, offset);
179
180					if (tickCount == 2)
181						_ToggleStyle(fItalicStyle);
182					else if (tickCount == 3)
183						_ToggleStyle(fBoldStyle);
184
185					// Don't include the ticks in the next span.
186					offset += tickCount - 1;
187					start = offset + 1;
188					c += tickCount - 1;
189				}
190				break;
191
192			case '=':
193				// Detect headings
194				if (offset == start
195					&& fCurrentParagraph.IsEmpty()
196					&& offset + 2 < charCount
197					&& c[0] == '=' && c[1] == ' ') {
198
199					fCurrentParagraph.SetStyle(fHeadingParagraphStyle);
200					fCurrentCharacterStyle = &fHeadingStyle;
201
202					offset += 2;
203					c += 2;
204
205					start = offset + 1;
206				} else if (offset > start
207					&& offset + 2 < charCount
208					&& c[0] == '=' && c[1] == '\n') {
209
210					_CopySpan(text, start, offset - 1);
211
212					offset += 2;
213					c += 2;
214
215					_FinishParagraph(offset >= charCount);
216
217					start = offset + 1;
218				}
219				break;
220
221			case ' ':
222				// Detect bullets at line starts (preceeding space)
223				if (offset == start
224					&& fCurrentParagraph.IsEmpty()
225					&& offset + 2 < charCount
226					&& (c[0] == '*' || c[0] == '-') && c[1] == ' ') {
227
228					fCurrentParagraph.SetStyle(fBulletStyle);
229
230					offset += 2;
231					c += 2;
232
233					start = offset + 1;
234				}
235				break;
236
237			case '*':
238			case '-':
239				// Detect bullets at line starts (no preceeding space)
240				if (offset == start
241					&& fCurrentParagraph.IsEmpty()
242					&& offset + 1 < charCount
243					&& c[0] == ' ') {
244
245					fCurrentParagraph.SetStyle(fBulletStyle);
246
247					offset += 1;
248					c += 1;
249
250					start = offset + 1;
251				}
252				break;
253
254			default:
255				break;
256
257		}
258		offset++;
259	}
260}
261
262
263void
264MarkupParser::_CopySpan(const BString& text, int32& start, int32 end)
265{
266	if (start >= end)
267		return;
268
269	BString subString;
270	text.CopyCharsInto(subString, start, end - start);
271	fCurrentParagraph.Append(TextSpan(subString, *fCurrentCharacterStyle));
272
273	start = end;
274}
275
276
277void
278MarkupParser::_ToggleStyle(const CharacterStyle& style)
279{
280	if (fCurrentCharacterStyle == &style)
281		fCurrentCharacterStyle = &fNormalStyle;
282	else
283		fCurrentCharacterStyle = &style;
284}
285
286
287void
288MarkupParser::_FinishParagraph(bool isLast)
289{
290	if (!isLast)
291		fCurrentParagraph.Append(TextSpan("\n", *fCurrentCharacterStyle));
292
293	if (fCurrentParagraph.IsEmpty()) {
294		// Append empty span
295		fCurrentParagraph.Append(TextSpan("", fNormalStyle));
296	}
297
298	fTextDocument->Append(fCurrentParagraph);
299	fCurrentParagraph.Clear();
300	fCurrentParagraph.SetStyle(fParagraphStyle);
301	fCurrentCharacterStyle = &fNormalStyle;
302}
303