summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2021-05-20 14:57:09 +0300
committerJaakko Keränen <jaakko.keranen@iki.fi>2021-05-20 14:57:09 +0300
commit5f9685010addd4a0f04c13f889856084381dd1c6 (patch)
treec29aecfd36da1ac0c3608fe9969841643ed5ca95
parentb8c07107fa332651cf2281fe8d2f90dfc122b588 (diff)
Added GmTypesetter
-rw-r--r--CMakeLists.txt2
-rw-r--r--src/defs.h6
-rw-r--r--src/gmdocument.c29
-rw-r--r--src/gmdocument.h9
-rw-r--r--src/gmtypesetter.c25
-rw-r--r--src/gmtypesetter.h41
-rw-r--r--src/ui/documentwidget.c14
7 files changed, 98 insertions, 28 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index a1037b0c..edee3d85 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -124,6 +124,8 @@ set (SOURCES
124 src/gmdocument.h 124 src/gmdocument.h
125 src/gmrequest.c 125 src/gmrequest.c
126 src/gmrequest.h 126 src/gmrequest.h
127 src/gmtypesetter.c
128 src/gmtypesetter.h
127 src/gmutil.c 129 src/gmutil.c
128 src/gmutil.h 130 src/gmutil.h
129 src/gopher.c 131 src/gopher.c
diff --git a/src/defs.h b/src/defs.h
index 71719f7a..0da404bf 100644
--- a/src/defs.h
+++ b/src/defs.h
@@ -24,6 +24,12 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
24 24
25#include "lang.h" 25#include "lang.h"
26 26
27enum iSourceFormat {
28 undefined_SourceFormat = -1,
29 gemini_SourceFormat = 0,
30 plainText_SourceFormat,
31};
32
27enum iFileVersion { 33enum iFileVersion {
28 initial_FileVersion = 0, 34 initial_FileVersion = 0,
29 addedResponseTimestamps_FileVersion = 1, 35 addedResponseTimestamps_FileVersion = 1,
diff --git a/src/gmdocument.c b/src/gmdocument.c
index 67adb9cc..f8d41172 100644
--- a/src/gmdocument.c
+++ b/src/gmdocument.c
@@ -21,6 +21,7 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ 21SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
22 22
23#include "gmdocument.h" 23#include "gmdocument.h"
24#include "gmtypesetter.h"
24#include "gmutil.h" 25#include "gmutil.h"
25#include "lang.h" 26#include "lang.h"
26#include "ui/color.h" 27#include "ui/color.h"
@@ -73,7 +74,7 @@ iDefineTypeConstruction(GmLink)
73 74
74struct Impl_GmDocument { 75struct Impl_GmDocument {
75 iObject object; 76 iObject object;
76 enum iGmDocumentFormat format; 77 enum iSourceFormat format;
77 iString unormSource; /* unnormalized source */ 78 iString unormSource; /* unnormalized source */
78 iString source; /* normalized source */ 79 iString source; /* normalized source */
79 iString url; /* for resolving relative links */ 80 iString url; /* for resolving relative links */
@@ -95,7 +96,7 @@ struct Impl_GmDocument {
95iDefineObjectConstruction(GmDocument) 96iDefineObjectConstruction(GmDocument)
96 97
97static enum iGmLineType lineType_GmDocument_(const iGmDocument *d, const iRangecc line) { 98static enum iGmLineType lineType_GmDocument_(const iGmDocument *d, const iRangecc line) {
98 if (d->format == plainText_GmDocumentFormat) { 99 if (d->format == plainText_SourceFormat) {
99 return text_GmLineType; 100 return text_GmLineType;
100 } 101 }
101 return lineType_Rangecc(line); 102 return lineType_Rangecc(line);
@@ -305,7 +306,7 @@ static void linkContentWasLaidOut_GmDocument_(iGmDocument *d, const iGmMediaInfo
305 306
306static iBool isNormalized_GmDocument_(const iGmDocument *d) { 307static iBool isNormalized_GmDocument_(const iGmDocument *d) {
307 const iPrefs *prefs = prefs_App(); 308 const iPrefs *prefs = prefs_App();
308 if (d->format == plainText_GmDocumentFormat) { 309 if (d->format == plainText_SourceFormat) {
309 return iTrue; /* tabs are always normalized in plain text */ 310 return iTrue; /* tabs are always normalized in plain text */
310 } 311 }
311 if (startsWithCase_String(&d->url, "gemini:") && prefs->monospaceGemini) { 312 if (startsWithCase_String(&d->url, "gemini:") && prefs->monospaceGemini) {
@@ -433,7 +434,7 @@ static void doLayout_GmDocument_(iGmDocument *d) {
433 enum iGmLineType prevType = text_GmLineType; 434 enum iGmLineType prevType = text_GmLineType;
434 enum iGmLineType prevNonBlankType = text_GmLineType; 435 enum iGmLineType prevNonBlankType = text_GmLineType;
435 iBool followsBlank = iFalse; 436 iBool followsBlank = iFalse;
436 if (d->format == plainText_GmDocumentFormat) { 437 if (d->format == plainText_SourceFormat) {
437 isPreformat = iTrue; 438 isPreformat = iTrue;
438 isFirstText = iFalse; 439 isFirstText = iFalse;
439 } 440 }
@@ -502,14 +503,14 @@ static void doLayout_GmDocument_(iGmDocument *d) {
502 if (contentLine.start == content.start) { 503 if (contentLine.start == content.start) {
503 prevType = type; 504 prevType = type;
504 } 505 }
505 if (d->format == gemini_GmDocumentFormat && 506 if (d->format == gemini_SourceFormat &&
506 startsWithSc_Rangecc(line, "```", &iCaseSensitive)) { 507 startsWithSc_Rangecc(line, "```", &iCaseSensitive)) {
507 isPreformat = iFalse; 508 isPreformat = iFalse;
508 addSiteBanner = iFalse; /* overrides the banner */ 509 addSiteBanner = iFalse; /* overrides the banner */
509 continue; 510 continue;
510 } 511 }
511 run.preId = preId; 512 run.preId = preId;
512 run.font = (d->format == plainText_GmDocumentFormat ? regularMonospace_FontId : preFont); 513 run.font = (d->format == plainText_SourceFormat ? regularMonospace_FontId : preFont);
513 indent = indents[type]; 514 indent = indents[type];
514 } 515 }
515 if (addSiteBanner) { 516 if (addSiteBanner) {
@@ -582,7 +583,7 @@ static void doLayout_GmDocument_(iGmDocument *d) {
582 } 583 }
583 } 584 }
584 /* Folded blocks are represented by a single run with the alt text. */ 585 /* Folded blocks are represented by a single run with the alt text. */
585 if (isPreformat && d->format != plainText_GmDocumentFormat) { 586 if (isPreformat && d->format != plainText_SourceFormat) {
586 const iGmPreMeta *meta = constAt_Array(&d->preMeta, preId - 1); 587 const iGmPreMeta *meta = constAt_Array(&d->preMeta, preId - 1);
587 if (meta->flags & folded_GmPreMetaFlag) { 588 if (meta->flags & folded_GmPreMetaFlag) {
588 const iBool isBlank = isEmpty_Range(&meta->altText); 589 const iBool isBlank = isEmpty_Range(&meta->altText);
@@ -678,7 +679,7 @@ static void doLayout_GmDocument_(iGmDocument *d) {
678 pushBack_Array(&d->layout, &icon); 679 pushBack_Array(&d->layout, &icon);
679 } 680 }
680 run.color = colors[type]; 681 run.color = colors[type];
681 if (d->format == plainText_GmDocumentFormat) { 682 if (d->format == plainText_SourceFormat) {
682 run.color = colors[text_GmLineType]; 683 run.color = colors[text_GmLineType];
683 } 684 }
684 /* Special formatting for the first paragraph (e.g., subtitle, introduction, or lede). */ 685 /* Special formatting for the first paragraph (e.g., subtitle, introduction, or lede). */
@@ -707,8 +708,8 @@ static void doLayout_GmDocument_(iGmDocument *d) {
707 type == quote_GmLineType ? 4 : 0); 708 type == quote_GmLineType ? 4 : 0);
708 } 709 }
709 const iBool isWordWrapped = 710 const iBool isWordWrapped =
710 (d->format == plainText_GmDocumentFormat ? prefs->plainTextWrap : !isPreformat); 711 (d->format == plainText_SourceFormat ? prefs->plainTextWrap : !isPreformat);
711 if (isPreformat && d->format != plainText_GmDocumentFormat) { 712 if (isPreformat && d->format != plainText_SourceFormat) {
712 /* Remember the top left coordinates of the block (first line of block). */ 713 /* Remember the top left coordinates of the block (first line of block). */
713 iGmPreMeta *meta = at_Array(&d->preMeta, preId - 1); 714 iGmPreMeta *meta = at_Array(&d->preMeta, preId - 1);
714 if (~meta->flags & topLeft_GmPreMetaFlag) { 715 if (~meta->flags & topLeft_GmPreMetaFlag) {
@@ -866,7 +867,7 @@ static void doLayout_GmDocument_(iGmDocument *d) {
866} 867}
867 868
868void init_GmDocument(iGmDocument *d) { 869void init_GmDocument(iGmDocument *d) {
869 d->format = gemini_GmDocumentFormat; 870 d->format = gemini_SourceFormat;
870 init_String(&d->unormSource); 871 init_String(&d->unormSource);
871 init_String(&d->source); 872 init_String(&d->source);
872 init_String(&d->url); 873 init_String(&d->url);
@@ -1397,7 +1398,7 @@ void setThemeSeed_GmDocument(iGmDocument *d, const iBlock *seed) {
1397#endif 1398#endif
1398} 1399}
1399 1400
1400void setFormat_GmDocument(iGmDocument *d, enum iGmDocumentFormat format) { 1401void setFormat_GmDocument(iGmDocument *d, enum iSourceFormat format) {
1401 d->format = format; 1402 d->format = format;
1402} 1403}
1403 1404
@@ -1439,7 +1440,7 @@ static void normalize_GmDocument(iGmDocument *d) {
1439 iRangecc src = range_String(&d->source); 1440 iRangecc src = range_String(&d->source);
1440 iRangecc line = iNullRange; 1441 iRangecc line = iNullRange;
1441 iBool isPreformat = iFalse; 1442 iBool isPreformat = iFalse;
1442 if (d->format == plainText_GmDocumentFormat) { 1443 if (d->format == plainText_SourceFormat) {
1443 isPreformat = iTrue; /* Cannot be turned off. */ 1444 isPreformat = iTrue; /* Cannot be turned off. */
1444 } 1445 }
1445 const int preTabWidth = 4; /* TODO: user-configurable parameter */ 1446 const int preTabWidth = 4; /* TODO: user-configurable parameter */
@@ -1467,7 +1468,7 @@ static void normalize_GmDocument(iGmDocument *d) {
1467 } 1468 }
1468 } 1469 }
1469 appendCStr_String(normalized, "\n"); 1470 appendCStr_String(normalized, "\n");
1470 if (d->format == gemini_GmDocumentFormat && 1471 if (d->format == gemini_SourceFormat &&
1471 lineType_GmDocument_(d, line) == preformatted_GmLineType) { 1472 lineType_GmDocument_(d, line) == preformatted_GmLineType) {
1472 isPreformat = iFalse; 1473 isPreformat = iFalse;
1473 } 1474 }
diff --git a/src/gmdocument.h b/src/gmdocument.h
index 1e54a16a..5137bb28 100644
--- a/src/gmdocument.h
+++ b/src/gmdocument.h
@@ -22,6 +22,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
22 22
23#pragma once 23#pragma once
24 24
25#include "defs.h"
25#include "gmutil.h" 26#include "gmutil.h"
26#include "media.h" 27#include "media.h"
27 28
@@ -148,12 +149,6 @@ iRangecc findLoc_GmRun (const iGmRun *, iInt2 pos);
148iDeclareClass(GmDocument) 149iDeclareClass(GmDocument)
149iDeclareObjectConstruction(GmDocument) 150iDeclareObjectConstruction(GmDocument)
150 151
151enum iGmDocumentFormat {
152 undefined_GmDocumentFormat = -1,
153 gemini_GmDocumentFormat = 0,
154 plainText_GmDocumentFormat,
155};
156
157enum iGmDocumentBanner { 152enum iGmDocumentBanner {
158 none_GmDocumentBanner, 153 none_GmDocumentBanner,
159 siteDomain_GmDocumentBanner, 154 siteDomain_GmDocumentBanner,
@@ -166,7 +161,7 @@ enum iGmDocumentUpdate {
166}; 161};
167 162
168void setThemeSeed_GmDocument (iGmDocument *, const iBlock *seed); 163void setThemeSeed_GmDocument (iGmDocument *, const iBlock *seed);
169void setFormat_GmDocument (iGmDocument *, enum iGmDocumentFormat format); 164void setFormat_GmDocument (iGmDocument *, enum iSourceFormat format);
170void setBanner_GmDocument (iGmDocument *, enum iGmDocumentBanner type); 165void setBanner_GmDocument (iGmDocument *, enum iGmDocumentBanner type);
171void setWidth_GmDocument (iGmDocument *, int width); 166void setWidth_GmDocument (iGmDocument *, int width);
172void redoLayout_GmDocument (iGmDocument *); 167void redoLayout_GmDocument (iGmDocument *);
diff --git a/src/gmtypesetter.c b/src/gmtypesetter.c
new file mode 100644
index 00000000..29a1bd93
--- /dev/null
+++ b/src/gmtypesetter.c
@@ -0,0 +1,25 @@
1/* Copyright 2021 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
23#include "gmtypesetter.h"
24#include "gmdocument.h"
25
diff --git a/src/gmtypesetter.h b/src/gmtypesetter.h
new file mode 100644
index 00000000..aba351dd
--- /dev/null
+++ b/src/gmtypesetter.h
@@ -0,0 +1,41 @@
1/* Copyright 2021 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
23#pragma once
24
25#include "defs.h"
26
27#include <the_Foundation/array.h>
28#include <the_Foundation/string.h>
29#include <the_Foundation/vec2.h>
30
31/* GmTypesetter has two jobs: it normalizes incoming source text, and typesets it as a
32 sequence of GmRuns. New data can be appended progressively. */
33
34iDeclareType(GmTypesetter)
35iDeclareTypeConstruction(GmTypesetter)
36
37void reset_GmTypesetter (iGmTypesetter *, enum iSourceFormat format);
38void setWidth_GmTypesetter (iGmTypesetter *, int width);
39void addInput_GmTypesetter (iGmTypesetter *, const iString *source);
40iBool getRuns_GmTypesetter (iGmTypesetter *, iArray *runs_out); /* returns false when no output generated */
41void skip_GmTypesetter (iGmTypesetter *, int ySkip);
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c
index 9169ea06..94337f8d 100644
--- a/src/ui/documentwidget.c
+++ b/src/ui/documentwidget.c
@@ -1076,7 +1076,7 @@ static void showErrorPage_DocumentWidget_(iDocumentWidget *d, enum iGmStatusCode
1076 } 1076 }
1077 } 1077 }
1078 setBanner_GmDocument(d->doc, useBanner ? bannerType_DocumentWidget_(d) : none_GmDocumentBanner); 1078 setBanner_GmDocument(d->doc, useBanner ? bannerType_DocumentWidget_(d) : none_GmDocumentBanner);
1079 setFormat_GmDocument(d->doc, gemini_GmDocumentFormat); 1079 setFormat_GmDocument(d->doc, gemini_SourceFormat);
1080 translate_Lang(src); 1080 translate_Lang(src);
1081 d->state = ready_RequestState; 1081 d->state = ready_RequestState;
1082 setSource_DocumentWidget(d, src); 1082 setSource_DocumentWidget(d, src);
@@ -1214,7 +1214,7 @@ static void updateDocument_DocumentWidget_(iDocumentWidget *d,
1214 if (isSuccess_GmStatusCode(statusCode)) { 1214 if (isSuccess_GmStatusCode(statusCode)) {
1215 /* Check the MIME type. */ 1215 /* Check the MIME type. */
1216 iRangecc charset = range_CStr("utf-8"); 1216 iRangecc charset = range_CStr("utf-8");
1217 enum iGmDocumentFormat docFormat = undefined_GmDocumentFormat; 1217 enum iSourceFormat docFormat = undefined_SourceFormat;
1218 const iString *mimeStr = collect_String(lower_String(&response->meta)); /* for convenience */ 1218 const iString *mimeStr = collect_String(lower_String(&response->meta)); /* for convenience */
1219 set_String(&d->sourceMime, mimeStr); 1219 set_String(&d->sourceMime, mimeStr);
1220 iRangecc mime = range_String(mimeStr); 1220 iRangecc mime = range_String(mimeStr);
@@ -1223,18 +1223,18 @@ static void updateDocument_DocumentWidget_(iDocumentWidget *d,
1223 iRangecc param = seg; 1223 iRangecc param = seg;
1224 trim_Rangecc(&param); 1224 trim_Rangecc(&param);
1225 if (equal_Rangecc(param, "text/gemini")) { 1225 if (equal_Rangecc(param, "text/gemini")) {
1226 docFormat = gemini_GmDocumentFormat; 1226 docFormat = gemini_SourceFormat;
1227 setRange_String(&d->sourceMime, param); 1227 setRange_String(&d->sourceMime, param);
1228 } 1228 }
1229 else if (startsWith_Rangecc(param, "text/") || 1229 else if (startsWith_Rangecc(param, "text/") ||
1230 equal_Rangecc(param, "application/json")) { 1230 equal_Rangecc(param, "application/json")) {
1231 docFormat = plainText_GmDocumentFormat; 1231 docFormat = plainText_SourceFormat;
1232 setRange_String(&d->sourceMime, param); 1232 setRange_String(&d->sourceMime, param);
1233 } 1233 }
1234 else if (equal_Rangecc(param, "application/zip") || 1234 else if (equal_Rangecc(param, "application/zip") ||
1235 (startsWith_Rangecc(param, "application/") && 1235 (startsWith_Rangecc(param, "application/") &&
1236 endsWithCase_Rangecc(param, "+zip"))) { 1236 endsWithCase_Rangecc(param, "+zip"))) {
1237 docFormat = gemini_GmDocumentFormat; 1237 docFormat = gemini_SourceFormat;
1238 setRange_String(&d->sourceMime, param); 1238 setRange_String(&d->sourceMime, param);
1239 iString *key = collectNew_String(); 1239 iString *key = collectNew_String();
1240 toString_Sym(SDLK_s, KMOD_PRIMARY, key); 1240 toString_Sym(SDLK_s, KMOD_PRIMARY, key);
@@ -1261,7 +1261,7 @@ static void updateDocument_DocumentWidget_(iDocumentWidget *d,
1261 startsWith_Rangecc(param, "audio/")) { 1261 startsWith_Rangecc(param, "audio/")) {
1262 const iBool isAudio = startsWith_Rangecc(param, "audio/"); 1262 const iBool isAudio = startsWith_Rangecc(param, "audio/");
1263 /* Make a simple document with an image or audio player. */ 1263 /* Make a simple document with an image or audio player. */
1264 docFormat = gemini_GmDocumentFormat; 1264 docFormat = gemini_SourceFormat;
1265 setRange_String(&d->sourceMime, param); 1265 setRange_String(&d->sourceMime, param);
1266 const iGmLinkId imgLinkId = 1; /* there's only the one link */ 1266 const iGmLinkId imgLinkId = 1; /* there's only the one link */
1267 /* TODO: Do the image loading in `postProcessRequestContent_DocumentWidget_()` */ 1267 /* TODO: Do the image loading in `postProcessRequestContent_DocumentWidget_()` */
@@ -1306,7 +1306,7 @@ static void updateDocument_DocumentWidget_(iDocumentWidget *d,
1306 } 1306 }
1307 } 1307 }
1308 } 1308 }
1309 if (docFormat == undefined_GmDocumentFormat) { 1309 if (docFormat == undefined_SourceFormat) {
1310 showErrorPage_DocumentWidget_(d, unsupportedMimeType_GmStatusCode, &response->meta); 1310 showErrorPage_DocumentWidget_(d, unsupportedMimeType_GmStatusCode, &response->meta);
1311 deinit_String(&str); 1311 deinit_String(&str);
1312 return; 1312 return;