1 /* Written by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
2 * Understanding is not required. Only obedience.
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
21 import std
.concurrency
;
25 import arsd
.simpledisplay
;
31 import iv
.nanovg
.oui
.blendish
;
41 // ////////////////////////////////////////////////////////////////////////// //
42 BookText
loadBook (string fname
) {
43 __gshared
bool eliteShipsLoaded
= false;
46 //import core.memory : GC;
47 fname
= fname
.expandTilde
.absolutePath
;
48 //writeln("loading '", fname, "'...");
50 auto stt
= MonoTime
.currTime
;
51 auto book
= new BookText(fname
);
54 { import std
.stdio
; writeln("loaded: '", fname
, "' in ", (MonoTime
.currTime
-stt
).total
!"msecs", " milliseconds"); }
59 foreach (string line
; VFile(buildPath(RcDir
, ".lastfile")).byLineCopy
) {
63 while (lines
.length
&& lines
[$-1].isComment
) {
64 comments
~= lines
[$-1];
65 lines
= lines
[0..$-1];
69 } catch (Exception
) {}
70 try { import std
.file
; mkdirRecurse(RcDir
); } catch (Exception
) {}
71 auto fo
= VFile(buildPath(RcDir
, ".lastfile"), "w");
72 foreach (string s
; lines
) fo
.writeln(s
);
73 foreach_reverse (string s
; comments
) fo
.writeln(s
);
76 stateFileName
= buildPath(RcDir
, fname
.baseName
~".rc");
78 if (!eliteShipsLoaded
) {
80 eliteShipsLoaded
= true;
87 // ////////////////////////////////////////////////////////////////////////// //
91 uint wordidx
; // first word
94 Anc
[] hrefs
; // `name` is dest
99 // ////////////////////////////////////////////////////////////////////////// //
100 public BookMetadata
formatBook (BookText book
, LayText lay
) {
101 assert(book
!is null);
103 BookMetadata meta
= new BookMetadata();
105 static void fixFont (LayText lay
) {
106 if (lay
.fontStyle
.italic
) {
107 if (lay
.fontStyle
.bold
) lay
.fontStyle
.fontface
= lay
.fontFaceId("textz");
108 else lay
.fontStyle
.fontface
= lay
.fontFaceId("texti");
109 } else if (lay
.fontStyle
.bold
) {
110 if (lay
.fontStyle
.italic
) lay
.fontStyle
.fontface
= lay
.fontFaceId("textz");
111 else lay
.fontStyle
.fontface
= lay
.fontFaceId("textb");
113 lay
.fontStyle
.fontface
= lay
.fontFaceId("text");
117 static void setNormalStyle (LayText lay
) {
118 lay
.fontStyle
.flags
= 0;
119 lay
.fontStyle
.fontsize
= fsizeText
;
120 lay
.fontStyle
.color
= colorText
.asUint
;
121 lay
.lineStyle
.leftpad
= 0;
122 lay
.lineStyle
.rightpad
= 0;
123 lay
.lineStyle
.paraIndent
= 3;
124 if (optJustify
) lay
.lineStyle
.setJustify
; else lay
.lineStyle
.setLeft
;
128 static void setTitleStyle (LayText lay
) {
129 lay
.fontStyle
.flags
= 0;
130 lay
.fontStyle
.fontsize
= fsizeText
+4;
131 lay
.fontStyle
.color
= colorText
.asUint
;
132 lay
.lineStyle
.leftpad
= 0;
133 lay
.lineStyle
.rightpad
= 0;
134 lay
.lineStyle
.paraIndent
= 0;
135 lay
.lineStyle
.setCenter
;
139 static void setSubtitleStyle (LayText lay
) {
140 lay
.fontStyle
.flags
= 0;
141 lay
.fontStyle
.fontsize
= fsizeText
+2;
142 lay
.fontStyle
.color
= colorText
.asUint
;
143 lay
.lineStyle
.leftpad
= 0;
144 lay
.lineStyle
.rightpad
= 0;
145 lay
.lineStyle
.paraIndent
= 0;
146 lay
.lineStyle
.setCenter
;
150 static void setCiteStyle (LayText lay
) {
151 lay
.fontStyle
.flags
= 0;
152 lay
.fontStyle
.fontsize
= fsizeText
;
153 lay
.fontStyle
.italic
= true;
154 lay
.fontStyle
.color
= colorText
.asUint
;
155 lay
.lineStyle
.leftpad
= fsizeText
*3;
156 lay
.lineStyle
.rightpad
= fsizeText
*3;
157 lay
.lineStyle
.paraIndent
= 3;
158 if (optJustify
) lay
.lineStyle
.setJustify
; else lay
.lineStyle
.setLeft
;
162 static void setEpigraphStyle (LayText lay
) {
163 lay
.fontStyle
.flags
= 0;
164 lay
.fontStyle
.fontsize
= fsizeText
;
165 lay
.fontStyle
.italic
= true;
166 lay
.fontStyle
.color
= colorText
.asUint
;
167 lay
.lineStyle
.leftpad
= lay
.width
/2;
168 lay
.lineStyle
.rightpad
= 0;
169 lay
.lineStyle
.paraIndent
= 0;
170 lay
.lineStyle
.setRight
;
174 static void setPoemStyle (LayText lay
) {
175 lay
.fontStyle
.flags
= 0;
176 lay
.fontStyle
.fontsize
= fsizeText
;
177 lay
.fontStyle
.italic
= true;
178 lay
.fontStyle
.color
= colorText
.asUint
;
179 lay
.lineStyle
.leftpad
= fsizeText
*3;
180 lay
.lineStyle
.rightpad
= 0;
181 lay
.lineStyle
.paraIndent
= 0;
182 lay
.lineStyle
.setLeft
;
186 void dumpTree (Tag ct
) {
187 while (ct
!is null) {
188 { import std
.stdio
; writeln("tag: ", ct
.name
); }
193 void badTag (Tag tag
, string msg
=null) {
194 import std
.string
: format
, indexOf
;
195 assert(tag
!is null);
197 if (msg
.length
== 0) msg
= "invalid tag: '%s'";
198 if (msg
.indexOf("%s") >= 0) {
199 throw new Exception(msg
.format(tag
.name
));
201 throw new Exception(msg
);
205 void saveTagId (Tag tag
) {
207 import std
.conv
: to
;
208 meta
.ids
~= BookMetadata
.Anc(tag
.id
.to
!dstring
, lay
.nextWordIndex
);
212 void putParaContentInternal (Tag ct
, int boldc
=0, int italicc
=0, int underc
=0) {
213 if (ct
is null) return;
214 auto c
= lay
.fontStyle
.color
;
215 scope(exit
) lay
.fontStyle
.color
= c
;
218 case "strong": ++boldc
; lay
.fontStyle
.bold
= true; break;
219 case "emphasis": ++italicc
; lay
.fontStyle
.italic
= true; break;
220 case "image": return;
221 case "style": return;
223 if (ct
.href
.length
) {
224 import std
.conv
: to
;
225 meta
.hrefs
~= BookMetadata
.Anc(ct
.href
.to
!dstring
, lay
.nextWordIndex
);
227 lay
.fontStyle
.underline
= true;
228 lay
.fontStyle
.color
= colorTextHref
.asUint
;
231 case "": lay
.put(ct
.text
); return;
235 foreach (Tag tag
; ct
.children
) putParaContentInternal(tag
, boldc
, italicc
, underc
);
237 case "strong": if (--boldc
== 0) lay
.fontStyle
.bold
= false; break;
238 case "emphasis": if (--italicc
== 0) lay
.fontStyle
.italic
= false; break;
239 case "a": if (--underc
== 0) lay
.fontStyle
.underline
= false; break;
245 void putTagContents (Tag ct
) {
246 if (ct
is null) return;
248 foreach (Tag tag
; ct
.children
) putParaContentInternal(tag
);
252 void putParas (Tag ct
) {
254 foreach (Tag tag
; ct
.children
) {
255 if (tag
.name
.length
== 0) continue;
256 if (tag
.name
== "p") putTagContents(tag
);
257 else if (tag
.name
== "empty-line") lay
.endLine();
258 else if (tag
.name
== "text-author") {}
263 void putAuthor (Tag ct
) {
265 foreach (Tag tag
; ct
.children
) {
266 if (tag
.name
.length
== 0) continue;
267 if (tag
.name
== "text-author") {
274 void putCite (Tag ct
) {
276 scope(exit
) lay
.popStyles
;
282 void putEpigraph (Tag ct
) {
284 scope(exit
) lay
.popStyles
;
285 setEpigraphStyle(lay
);
290 void putStanza (Tag ct
) {
292 foreach (Tag tag
; ct
.children
) {
293 if (tag
.name
.length
== 0) continue;
294 if (tag
.name
== "text-author") continue;
295 if (tag
.name
== "title") badTag(tag
, "titles in stanzas are not supported yet");
296 if (tag
.name
== "subtitle") badTag(tag
, "subtitles in stanzas are not supported yet");
297 if (tag
.name
== "epigraph") badTag(tag
, "epigraphs in poems are not supported yet");
298 if (tag
.name
== "date") continue;
299 if (tag
.name
== "v") { putTagContents(tag
); continue; }
304 void putPoem (Tag ct
) {
307 scope(exit
) lay
.popStyles
;
309 // put epigraph (not yet)
310 // put title and subtitle
311 foreach (Tag tag
; ct
.children
) {
312 if (tag
.name
== "title") {
314 scope(exit
) lay
.popStyles
;
316 lay
.endPara(); // space
317 } else if (tag
.name
== "subtitle") {
319 scope(exit
) lay
.popStyles
;
321 lay
.endPara(); // space
324 foreach (Tag tag
; ct
.children
) {
325 if (tag
.name
.length
== 0) continue;
326 if (tag
.name
== "text-author") continue;
327 if (tag
.name
== "title") continue;
328 if (tag
.name
== "subtitle") continue;
329 if (tag
.name
== "epigraph") badTag(tag
, "epigraphs in poems are not supported yet");
330 if (tag
.name
== "date") continue;
331 if (tag
.name
== "stanza") { putStanza(tag
); lay
.put(LayText
.EndParaCh
); continue; }
337 void putSection (Tag sc
) {
338 bool sectionRegistered
= false;
340 void registerSection (Tag tag
) {
341 import std
.conv
: to
;
342 if (sectionRegistered
) return;
343 sectionRegistered
= true;
344 string text
= tag
.textContent
.xstrip
;
345 //lay.sections ~= lay.curWordIndex;
347 while (text
.length
) {
348 char ch
= text
.ptr
[0];
350 if (ch
<= ' ' || ch
== 127) {
351 if (name
.length
== 0) continue;
352 if (ch
!= '\n') ch
= ' ';
354 if (name
[$-1] > ' ') name
~= "\n...";
357 if (name
[$-1] > ' ') name
~= ' ';
364 if (name
.length
== 0) name
= "* * *";
365 meta
.sections
~= BookMetadata
.Anc(name
.to
!dstring
, lay
.nextWordIndex
);
369 foreach (Tag tag
; sc
.children
) {
370 if (tag
.name
.length
== 0) continue;
371 if (tag
.name
== "title") {
373 scope(exit
) lay
.popStyles
;
375 registerSection(tag
);
377 } else if (tag
.name
== "subtitle") {
379 scope(exit
) lay
.popStyles
;
380 setSubtitleStyle(lay
);
381 registerSection(tag
);
383 } else if (tag
.name
== "epigraph") {
385 scope(exit
) lay
.popStyles
;
386 setEpigraphStyle(lay
);
388 } else if (tag
.name
== "section") {
390 } else if (tag
.name
== "p") {
392 } else if (tag
.name
== "empty-line") {
393 lay
.put(LayText
.EndParaCh
);
394 } else if (tag
.name
== "cite") {
396 } else if (tag
.name
== "table") {
398 scope(exit
) lay
.popStyles
;
399 lay
.fontStyle
.fontsize
+= 8;
400 lay
.lineStyle
.setCenter
;
401 lay
.put("TABLE SKIPPED");
403 } else if (tag
.name
== "poem") {
405 } else if (tag
.name
== "image") {
406 } else if (tag
.name
== "style") {
413 foreach (Tag tag
; book
.content
.children
) {
414 if (tag
.name
== "title") {
416 scope(exit
) lay
.popStyles
;
421 } else if (tag
.name
== "subtitle") {
423 scope(exit
) lay
.popStyles
;
427 } else if (tag
.name
== "epigraph") {
429 } else if (tag
.name
== "section") {
431 scope(exit
) lay
.popStyles
;
443 // ////////////////////////////////////////////////////////////////////////// //
444 private __gshared LayFontStash laf
; // layouter font stash
447 // ////////////////////////////////////////////////////////////////////////// //
448 private void loadFmtFonts () {
449 laf
= new LayFontStash();
451 laf
.addFont("text", textFontName
);
452 laf
.addFont("texti", textiFontName
);
453 laf
.addFont("textb", textbFontName
);
454 laf
.addFont("textz", textzFontName
);
456 laf
.addFont("mono", monoFontName
);
457 laf
.addFont("monoi", monoiFontName
);
458 laf
.addFont("monob", monobFontName
);
459 laf
.addFont("monoz", monozFontName
);
463 // ////////////////////////////////////////////////////////////////////////// //
465 struct ReformatWork
{
466 shared(BookText
) booktext
;
471 struct ReformatWorkComplete
{
473 shared(BookText
) booktext
;
474 shared(LayText
) laytext
;
475 shared(BookMetadata
) meta
;
482 // ////////////////////////////////////////////////////////////////////////// //
483 void reformatThreadFn (Tid ownerTid
) {
488 int newW
= -1, newH
= -1;
495 //{ import std.stdio; writeln("reformat request received..."); }
496 book
= cast(BookText
)w
.booktext
;
497 newFileName
= w
.bookFileName
;
500 if (newW
< 1) newW
= 1;
501 if (newH
< 1) newH
= 1;
507 if (!doQuit
&& newW
> 0 && newH
> 0) {
510 //writeln("loading new book: '", newFileName, "'");
511 book
= loadBook(newFileName
);
514 int maxWidth
= newW
-4-2-BND_SCROLLBAR_WIDTH
-2;
515 if (maxWidth
< 64) maxWidth
= 64;
518 //writeln("layouting...");
519 auto stt
= MonoTime
.currTime
;
520 auto lay
= new LayText(laf
, maxWidth
);
521 lay
.fontStyle
.color
= colorText
.asUint
;
523 auto meta
= book
.formatBook(lay
);
525 { import std
.stdio
; writeln("layouted in ", (MonoTime
.currTime
-stt
).total
!"msecs", " milliseconds"); }
526 auto res
= ReformatWorkComplete(newW
, newH
, cast(shared)book
, cast(shared)lay
, cast(shared)meta
);
528 } catch (Throwable e
) {
529 // here, we are dead and fucked (the exact order doesn't matter)
530 import core
.stdc
.stdlib
: abort
;
531 import core
.stdc
.stdio
: fprintf
, stderr
;
532 import core
.memory
: GC
;
533 import core
.thread
: thread_suspendAll
;
534 GC
.disable(); // yeah
535 thread_suspendAll(); // stop right here, you criminal scum!
536 auto s
= e
.toString();
537 fprintf(stderr
, "\n=== FATAL ===\n%.*s\n", cast(uint)s
.length
, s
.ptr
);
538 abort(); // die, you bitch!
542 send(ownerTid
, QuitWork());