summaryrefslogtreecommitdiff
path: root/src/gmdocument.c
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2020-09-02 11:25:03 +0300
committerJaakko Keränen <jaakko.keranen@iki.fi>2020-09-02 11:25:03 +0300
commit1e548eac8c63922315f89c96436b622615c488c7 (patch)
treed2c36fdaaa0bcc42cbc34bb804de5a7201dba2b1 /src/gmdocument.c
parent8ed2faadc4ca2497fc3b2e9d2eae6d40921de918 (diff)
parente2b5ea14d25dbbb62a1e827803e67c30df79c6a1 (diff)
Merge branch 'master' of skyjake.fi:skyjake/lagrange
Diffstat (limited to 'src/gmdocument.c')
-rw-r--r--src/gmdocument.c160
1 files changed, 96 insertions, 64 deletions
diff --git a/src/gmdocument.c b/src/gmdocument.c
index a6fc39c5..fc49dac4 100644
--- a/src/gmdocument.c
+++ b/src/gmdocument.c
@@ -1,3 +1,25 @@
1/* Copyright 2020 Jaakko Keränen <jaakko.keranen@iki.fi>
2
3Redistribution and use in source and binary forms, with or without
4modification, are permitted provided that the following conditions are met:
5
61. Redistributions of source code must retain the above copyright notice, this
7 list of conditions and the following disclaimer.
82. Redistributions in binary form must reproduce the above copyright notice,
9 this list of conditions and the following disclaimer in the documentation
10 and/or other materials provided with the distribution.
11
12THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
13ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
14WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
15DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
16ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
17(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
18LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
19ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
20(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
21SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
22
1#include "gmdocument.h" 23#include "gmdocument.h"
2#include "gmutil.h" 24#include "gmutil.h"
3#include "ui/color.h" 25#include "ui/color.h"
@@ -106,11 +128,11 @@ enum iGmLineType {
106 max_GmLineType, 128 max_GmLineType,
107}; 129};
108 130
109static enum iGmLineType lineType_GmDocument_(const iGmDocument *d, const iRangecc *line) { 131static enum iGmLineType lineType_GmDocument_(const iGmDocument *d, const iRangecc line) {
110 if (d->format == plainText_GmDocumentFormat) { 132 if (d->format == plainText_GmDocumentFormat) {
111 return text_GmLineType; 133 return text_GmLineType;
112 } 134 }
113 if (isEmpty_Range(line)) { 135 if (isEmpty_Range(&line)) {
114 return text_GmLineType; 136 return text_GmLineType;
115 } 137 }
116 if (startsWith_Rangecc(line, "=>")) { 138 if (startsWith_Rangecc(line, "=>")) {
@@ -128,10 +150,10 @@ static enum iGmLineType lineType_GmDocument_(const iGmDocument *d, const iRangec
128 if (startsWith_Rangecc(line, "```")) { 150 if (startsWith_Rangecc(line, "```")) {
129 return preformatted_GmLineType; 151 return preformatted_GmLineType;
130 } 152 }
131 if (*line->start == '>') { 153 if (*line.start == '>') {
132 return quote_GmLineType; 154 return quote_GmLineType;
133 } 155 }
134 if (size_Range(line) >= 2 && line->start[0] == '*' && isspace(line->start[1])) { 156 if (size_Range(&line) >= 2 && line.start[0] == '*' && isspace(line.start[1])) {
135 return bullet_GmLineType; 157 return bullet_GmLineType;
136 } 158 }
137 return text_GmLineType; 159 return text_GmLineType;
@@ -157,11 +179,11 @@ static int lastVisibleRunBottom_GmDocument_(const iGmDocument *d) {
157iInt2 measurePreformattedBlock_GmDocument_(const iGmDocument *d, const char *start, int font) { 179iInt2 measurePreformattedBlock_GmDocument_(const iGmDocument *d, const char *start, int font) {
158 const iRangecc content = { start, constEnd_String(&d->source) }; 180 const iRangecc content = { start, constEnd_String(&d->source) };
159 iRangecc line = iNullRange; 181 iRangecc line = iNullRange;
160 nextSplit_Rangecc(&content, "\n", &line); 182 nextSplit_Rangecc(content, "\n", &line);
161 iAssert(startsWith_Rangecc(&line, "```")); 183 iAssert(startsWith_Rangecc(line, "```"));
162 iRangecc preBlock = { line.end + 1, line.end + 1 }; 184 iRangecc preBlock = { line.end + 1, line.end + 1 };
163 while (nextSplit_Rangecc(&content, "\n", &line)) { 185 while (nextSplit_Rangecc(content, "\n", &line)) {
164 if (startsWith_Rangecc(&line, "```")) { 186 if (startsWith_Rangecc(line, "```")) {
165 break; 187 break;
166 } 188 }
167 preBlock.end = line.end; 189 preBlock.end = line.end;
@@ -170,31 +192,35 @@ iInt2 measurePreformattedBlock_GmDocument_(const iGmDocument *d, const char *sta
170} 192}
171 193
172static iRangecc addLink_GmDocument_(iGmDocument *d, iRangecc line, iGmLinkId *linkId) { 194static iRangecc addLink_GmDocument_(iGmDocument *d, iRangecc line, iGmLinkId *linkId) {
173 iRegExp *pattern = new_RegExp("=>\\s*([^\\s]+)(\\s.*)?", caseInsensitive_RegExpOption); 195 static iRegExp *pattern_;
196 if (!pattern_) {
197 pattern_ = new_RegExp("=>\\s*([^\\s]+)(\\s.*)?", caseInsensitive_RegExpOption);
198 }
174 iRegExpMatch m; 199 iRegExpMatch m;
175 if (matchRange_RegExp(pattern, line, &m)) { 200 init_RegExpMatch(&m);
201 if (matchRange_RegExp(pattern_, line, &m)) {
176 iGmLink *link = new_GmLink(); 202 iGmLink *link = new_GmLink();
177 setRange_String(&link->url, capturedRange_RegExpMatch(&m, 1)); 203 setRange_String(&link->url, capturedRange_RegExpMatch(&m, 1));
178 set_String(&link->url, absoluteUrl_String(&d->url, &link->url)); 204 set_String(&link->url, absoluteUrl_String(&d->url, &link->url));
179 /* Check the URL. */ { 205 /* Check the URL. */ {
180 iUrl parts; 206 iUrl parts;
181 init_Url(&parts, &link->url); 207 init_Url(&parts, &link->url);
182 if (!equalCase_Rangecc(&parts.host, cstr_String(&d->localHost))) { 208 if (!equalCase_Rangecc(parts.host, cstr_String(&d->localHost))) {
183 link->flags |= remote_GmLinkFlag; 209 link->flags |= remote_GmLinkFlag;
184 } 210 }
185 if (startsWithCase_Rangecc(&parts.protocol, "gemini")) { 211 if (startsWithCase_Rangecc(parts.scheme, "gemini")) {
186 link->flags |= gemini_GmLinkFlag; 212 link->flags |= gemini_GmLinkFlag;
187 } 213 }
188 else if (startsWithCase_Rangecc(&parts.protocol, "http")) { 214 else if (startsWithCase_Rangecc(parts.scheme, "http")) {
189 link->flags |= http_GmLinkFlag; 215 link->flags |= http_GmLinkFlag;
190 } 216 }
191 else if (equalCase_Rangecc(&parts.protocol, "gopher")) { 217 else if (equalCase_Rangecc(parts.scheme, "gopher")) {
192 link->flags |= gopher_GmLinkFlag; 218 link->flags |= gopher_GmLinkFlag;
193 } 219 }
194 else if (equalCase_Rangecc(&parts.protocol, "file")) { 220 else if (equalCase_Rangecc(parts.scheme, "file")) {
195 link->flags |= file_GmLinkFlag; 221 link->flags |= file_GmLinkFlag;
196 } 222 }
197 else if (equalCase_Rangecc(&parts.protocol, "data")) { 223 else if (equalCase_Rangecc(parts.scheme, "data")) {
198 link->flags |= data_GmLinkFlag; 224 link->flags |= data_GmLinkFlag;
199 } 225 }
200 /* Check the file name extension, if present. */ 226 /* Check the file name extension, if present. */
@@ -232,7 +258,6 @@ static iRangecc addLink_GmDocument_(iGmDocument *d, iRangecc line, iGmLinkId *li
232 line = capturedRange_RegExpMatch(&m, 1); /* Show the URL. */ 258 line = capturedRange_RegExpMatch(&m, 1); /* Show the URL. */
233 } 259 }
234 } 260 }
235 iRelease(pattern);
236 return line; 261 return line;
237} 262}
238 263
@@ -254,16 +279,21 @@ static size_t findLinkImage_GmDocument_(const iGmDocument *d, iGmLinkId linkId)
254 return iInvalidPos; 279 return iInvalidPos;
255} 280}
256 281
282static iBool isGopher_GmDocument_(const iGmDocument *d) {
283 return equalCase_Rangecc(urlScheme_String(&d->url), "gopher");
284}
285
257static void doLayout_GmDocument_(iGmDocument *d) { 286static void doLayout_GmDocument_(iGmDocument *d) {
287 const iBool isGemini = !isGopher_GmDocument_(d);
258 /* TODO: Collect these parameters into a GmTheme. */ 288 /* TODO: Collect these parameters into a GmTheme. */
259 static const int fonts[max_GmLineType] = { 289 const int fonts[max_GmLineType] = {
260 paragraph_FontId, 290 isGemini ? paragraph_FontId : preformatted_FontId,
261 paragraph_FontId, /* bullet */ 291 isGemini ? paragraph_FontId : preformatted_FontId, /* bullet */
262 preformatted_FontId, 292 preformatted_FontId,
263 quote_FontId, 293 quote_FontId,
264 heading1_FontId, 294 isGemini ? heading1_FontId : preformatted_FontId,
265 heading2_FontId, 295 isGemini ? heading2_FontId : preformatted_FontId,
266 heading3_FontId, 296 isGemini ? heading3_FontId : preformatted_FontId,
267 regular_FontId, 297 regular_FontId,
268 }; 298 };
269 static const int colors[max_GmLineType] = { 299 static const int colors[max_GmLineType] = {
@@ -277,7 +307,7 @@ static void doLayout_GmDocument_(iGmDocument *d) {
277 tmLinkText_ColorId, 307 tmLinkText_ColorId,
278 }; 308 };
279 static const int indents[max_GmLineType] = { 309 static const int indents[max_GmLineType] = {
280 5, 10, 5, 10, 0, 0, 0, 5 310 6, 12, 6, 12, 0, 0, 0, 6
281 }; 311 };
282 static const float topMargin[max_GmLineType] = { 312 static const float topMargin[max_GmLineType] = {
283 0.0f, 0.5f, 1.0f, 0.5f, 2.0f, 2.0f, 1.5f, 1.0f 313 0.0f, 0.5f, 1.0f, 0.5f, 2.0f, 2.0f, 1.5f, 1.0f
@@ -298,20 +328,21 @@ static void doLayout_GmDocument_(iGmDocument *d) {
298 return; 328 return;
299 } 329 }
300 const iRangecc content = range_String(&d->source); 330 const iRangecc content = range_String(&d->source);
331 iRangecc contentLine = iNullRange;
301 iInt2 pos = zero_I2(); 332 iInt2 pos = zero_I2();
302 iRangecc line = iNullRange; 333 iBool isFirstText = isGemini;
303 iRangecc preAltText = iNullRange;
304 enum iGmLineType prevType; // = text_GmLineType;
305 iBool isPreformat = iFalse; 334 iBool isPreformat = iFalse;
306 iBool isFirstText = iTrue; 335 iRangecc preAltText = iNullRange;
336 int preFont = preformatted_FontId;
307 iBool enableIndents = iFalse; 337 iBool enableIndents = iFalse;
308 iBool addSiteBanner = iTrue; 338 iBool addSiteBanner = iTrue;
309 int preFont = preformatted_FontId; 339 enum iGmLineType prevType;
310 if (d->format == plainText_GmDocumentFormat) { 340 if (d->format == plainText_GmDocumentFormat) {
311 isPreformat = iTrue; 341 isPreformat = iTrue;
312 isFirstText = iFalse; 342 isFirstText = iFalse;
313 } 343 }
314 while (nextSplit_Rangecc(&content, "\n", &line)) { 344 while (nextSplit_Rangecc(content, "\n", &contentLine)) {
345 iRangecc line = contentLine; /* `line` will be trimmed later; would confuse nextSplit */
315 iGmRun run; 346 iGmRun run;
316 run.flags = 0; 347 run.flags = 0;
317 run.color = white_ColorId; 348 run.color = white_ColorId;
@@ -320,7 +351,7 @@ static void doLayout_GmDocument_(iGmDocument *d) {
320 enum iGmLineType type; 351 enum iGmLineType type;
321 int indent = 0; 352 int indent = 0;
322 if (!isPreformat) { 353 if (!isPreformat) {
323 type = lineType_GmDocument_(d, &line); 354 type = lineType_GmDocument_(d, line);
324 if (line.start == content.start) { 355 if (line.start == content.start) {
325 prevType = type; 356 prevType = type;
326 } 357 }
@@ -358,7 +389,7 @@ static void doLayout_GmDocument_(iGmDocument *d) {
358 /* Preformatted line. */ 389 /* Preformatted line. */
359 type = preformatted_GmLineType; 390 type = preformatted_GmLineType;
360 if (d->format == gemini_GmDocumentFormat && 391 if (d->format == gemini_GmDocumentFormat &&
361 startsWithSc_Rangecc(&line, "```", &iCaseSensitive)) { 392 startsWithSc_Rangecc(line, "```", &iCaseSensitive)) {
362 isPreformat = iFalse; 393 isPreformat = iFalse;
363 preAltText = iNullRange; 394 preAltText = iNullRange;
364 addSiteBanner = iFalse; /* overrides the banner */ 395 addSiteBanner = iFalse; /* overrides the banner */
@@ -664,6 +695,20 @@ void setThemeSeed_GmDocument(iGmDocument *d, const iBlock *seed) {
664 } 695 }
665 /* Set up colors. */ 696 /* Set up colors. */
666 if (d->themeSeed) { 697 if (d->themeSeed) {
698 enum iHue {
699 red_Hue,
700 reddishOrange_Hue,
701 yellowishOrange_Hue,
702 yellow_Hue,
703 greenishYellow_Hue,
704 green_Hue,
705 bluishGreen_Hue,
706 cyan_Hue,
707 skyBlue_Hue,
708 blue_Hue,
709 violet_Hue,
710 pink_Hue
711 };
667 static const float hues[] = { 5, 25, 40, 56, 80, 120, 160, 180, 208, 231, 270, 324 }; 712 static const float hues[] = { 5, 25, 40, 56, 80, 120, 160, 180, 208, 231, 270, 324 };
668 static const struct { 713 static const struct {
669 int index[2]; 714 int index[2];
@@ -681,9 +726,9 @@ void setThemeSeed_GmDocument(iGmDocument *d, const iBlock *seed) {
681 { 8, 9 }, /* violet */ 726 { 8, 9 }, /* violet */
682 { 7, 8 }, /* pink */ 727 { 7, 8 }, /* pink */
683 }; 728 };
684 const float saturationLevel = 1.0f; /* TODO: user setting */
685 const iBool isBannerLighter = (d->themeSeed & 0x4000) != 0; 729 const iBool isBannerLighter = (d->themeSeed & 0x4000) != 0;
686 const size_t primIndex = d->themeSeed ? (d->themeSeed & 0xff) % iElemCount(hues) : 2; 730 const size_t primIndex = d->themeSeed ? (d->themeSeed & 0xff) % iElemCount(hues) : 2;
731 const float saturationLevel = 1.0f; /* TODO: user setting */
687 const iBool isDarkBgSat = 732 const iBool isDarkBgSat =
688 (d->themeSeed & 0x200000) != 0 && (primIndex < 1 || primIndex > 4); 733 (d->themeSeed & 0x200000) != 0 && (primIndex < 1 || primIndex > 4);
689 iHSLColor base = { hues[primIndex], 734 iHSLColor base = { hues[primIndex],
@@ -692,9 +737,10 @@ void setThemeSeed_GmDocument(iGmDocument *d, const iBlock *seed) {
692 1.0f }; 737 1.0f };
693 // printf("background: %d %f %f\n", (int) base.hue, base.sat, base.lum); 738 // printf("background: %d %f %f\n", (int) base.hue, base.sat, base.lum);
694 // printf("isDarkBgSat: %d\n", isDarkBgSat); 739 // printf("isDarkBgSat: %d\n", isDarkBgSat);
695 setHsl_Color(tmBackground_ColorId, base); 740 iHSLColor bgBase = base;
741 setHsl_Color(tmBackground_ColorId, bgBase);
696 742
697 setHsl_Color(tmBannerBackground_ColorId, addSatLum_HSLColor(base, 0.1f, 0.04f * (isBannerLighter ? 1 : -1))); 743 setHsl_Color(tmBannerBackground_ColorId, addSatLum_HSLColor(bgBase, 0.1f, 0.04f * (isBannerLighter ? 1 : -1)));
698 setHsl_Color(tmBannerTitle_ColorId, setLum_HSLColor(addSatLum_HSLColor(base, 0.1f, 0), 0.55f)); 744 setHsl_Color(tmBannerTitle_ColorId, setLum_HSLColor(addSatLum_HSLColor(base, 0.1f, 0), 0.55f));
699 setHsl_Color(tmBannerIcon_ColorId, setLum_HSLColor(addSatLum_HSLColor(base, 0.35f, 0), 0.65f)); 745 setHsl_Color(tmBannerIcon_ColorId, setLum_HSLColor(addSatLum_HSLColor(base, 0.35f, 0), 0.65f));
700 746
@@ -714,7 +760,7 @@ void setThemeSeed_GmDocument(iGmDocument *d, const iBlock *seed) {
714 setHsl_Color(tmHeading3_ColorId, setLum_HSLColor(altBase, titleLum + 0.60f)); 760 setHsl_Color(tmHeading3_ColorId, setLum_HSLColor(altBase, titleLum + 0.60f));
715 761
716 setHsl_Color(tmParagraph_ColorId, addSatLum_HSLColor(base, 0.1f, 0.6f)); 762 setHsl_Color(tmParagraph_ColorId, addSatLum_HSLColor(base, 0.1f, 0.6f));
717 setHsl_Color(tmFirstParagraph_ColorId, addSatLum_HSLColor(base, 0.2f, 0.8f)); 763 setHsl_Color(tmFirstParagraph_ColorId, addSatLum_HSLColor(base, 0.2f, 0.72f));
718 setHsl_Color(tmPreformatted_ColorId, (iHSLColor){ altHue2, 1.0f, 0.75f, 1.0f }); 764 setHsl_Color(tmPreformatted_ColorId, (iHSLColor){ altHue2, 1.0f, 0.75f, 1.0f });
719 set_Color(tmQuote_ColorId, get_Color(tmPreformatted_ColorId)); 765 set_Color(tmQuote_ColorId, get_Color(tmPreformatted_ColorId));
720 set_Color(tmInlineContentMetadata_ColorId, get_Color(tmHeading3_ColorId)); 766 set_Color(tmInlineContentMetadata_ColorId, get_Color(tmHeading3_ColorId));
@@ -736,31 +782,12 @@ void setThemeSeed_GmDocument(iGmDocument *d, const iBlock *seed) {
736 else if (i == tmHeading3_ColorId) { 782 else if (i == tmHeading3_ColorId) {
737 color.lum *= 0.75f; 783 color.lum *= 0.75f;
738 } 784 }
739#if 0
740 else if (isLink_ColorId(i)) {
741 /* Darken links generally to improve visibility against a
742 light background. */
743 color.lum *= 0.5f;
744 color.sat = 1.0f;
745 }
746#endif
747 else if (i == tmBannerIcon_ColorId || i == tmBannerTitle_ColorId) { 785 else if (i == tmBannerIcon_ColorId || i == tmBannerTitle_ColorId) {
748 if (isBannerLighter) { 786 color.sat = 1.0f;
749 color.lum *= 0.75f; 787 color.lum = 0.35f;
750 }
751 else {
752 color.lum = 0.98f;
753 }
754 } 788 }
755 else if (i == tmBannerBackground_ColorId) { 789 else if (i == tmBannerBackground_ColorId) {
756 if (isBannerLighter) { 790 color = hsl_Color(get_Color(tmBackground_ColorId));
757 //color.lum = iMin(0.9, color.lum);
758 color = hsl_Color(get_Color(tmBackground_ColorId));
759 }
760 else {
761 color.sat *= 0.8f;
762 color.lum = 0.6f;
763 }
764 } 791 }
765 else if (isText_ColorId(i)) { 792 else if (isText_ColorId(i)) {
766 color.sat = 0.9f; 793 color.sat = 0.9f;
@@ -772,7 +799,12 @@ void setThemeSeed_GmDocument(iGmDocument *d, const iBlock *seed) {
772 if (isDarkBgSat) { 799 if (isDarkBgSat) {
773 /* Saturate background, desaturate text. */ 800 /* Saturate background, desaturate text. */
774 if (isBackground_ColorId(i)) { 801 if (isBackground_ColorId(i)) {
775 color.sat = (color.sat + 1) / 2; 802 if (primIndex != green_Hue) {
803 color.sat = (color.sat + 1) / 2;
804 }
805 else {
806 color.sat *= 0.5f;
807 }
776 color.lum *= 0.75f; 808 color.lum *= 0.75f;
777 } 809 }
778 else if (isText_ColorId(i)) { 810 else if (isText_ColorId(i)) {
@@ -822,11 +854,11 @@ static void normalize_GmDocument(iGmDocument *d) {
822 iRangecc src = range_String(&d->source); 854 iRangecc src = range_String(&d->source);
823 iRangecc line = iNullRange; 855 iRangecc line = iNullRange;
824 iBool isPreformat = iFalse; 856 iBool isPreformat = iFalse;
825 if (d->format == plainText_GmDocumentFormat) { 857 if (d->format == plainText_GmDocumentFormat || isGopher_GmDocument_(d)) {
826 isPreformat = iTrue; /* Cannot be turned off. */ 858 isPreformat = iTrue; /* Cannot be turned off. */
827 } 859 }
828 const int preTabWidth = 8; /* TODO: user-configurable parameter */ 860 const int preTabWidth = 4; /* TODO: user-configurable parameter */
829 while (nextSplit_Rangecc(&src, "\n", &line)) { 861 while (nextSplit_Rangecc(src, "\n", &line)) {
830 if (isPreformat) { 862 if (isPreformat) {
831 /* Replace any tab characters with spaces for visualization. */ 863 /* Replace any tab characters with spaces for visualization. */
832 for (const char *ch = line.start; ch != line.end; ch++) { 864 for (const char *ch = line.start; ch != line.end; ch++) {
@@ -842,12 +874,12 @@ static void normalize_GmDocument(iGmDocument *d) {
842 } 874 }
843 } 875 }
844 appendCStr_String(normalized, "\n"); 876 appendCStr_String(normalized, "\n");
845 if (lineType_GmDocument_(d, &line) == preformatted_GmLineType) { 877 if (lineType_GmDocument_(d, line) == preformatted_GmLineType) {
846 isPreformat = iFalse; 878 isPreformat = iFalse;
847 } 879 }
848 continue; 880 continue;
849 } 881 }
850 if (lineType_GmDocument_(d, &line) == preformatted_GmLineType) { 882 if (lineType_GmDocument_(d, line) == preformatted_GmLineType) {
851 isPreformat = iTrue; 883 isPreformat = iTrue;
852 appendRange_String(normalized, line); 884 appendRange_String(normalized, line);
853 appendCStr_String(normalized, "\n"); 885 appendCStr_String(normalized, "\n");