xref: /haiku/src/apps/haikudepot/textview/MarkupParser.cpp (revision c237c4ce593ee823d9867fd997e51e4c447f5623)
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 
15 MarkupParser::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 
35 MarkupParser::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 
56 void
57 MarkupParser::SetStyles(const CharacterStyle& characterStyle,
58 	const ParagraphStyle& paragraphStyle)
59 {
60 	fNormalStyle = characterStyle;
61 	fParagraphStyle = paragraphStyle;
62 	_InitStyles();
63 }
64 
65 
66 TextDocumentRef
67 MarkupParser::CreateDocumentFromMarkup(const BString& text)
68 {
69 	TextDocumentRef document(new(std::nothrow) TextDocument(), true);
70 	if (!document.IsSet())
71 		return document;
72 
73 	AppendMarkup(document, text);
74 
75 	return document;
76 }
77 
78 
79 void
80 MarkupParser::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 
99 void
100 MarkupParser::_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 
129 void
130 MarkupParser::_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 
263 void
264 MarkupParser::_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 
277 void
278 MarkupParser::_ToggleStyle(const CharacterStyle& style)
279 {
280 	if (fCurrentCharacterStyle == &style)
281 		fCurrentCharacterStyle = &fNormalStyle;
282 	else
283 		fCurrentCharacterStyle = &style;
284 }
285 
286 
287 void
288 MarkupParser::_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