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/>.
22 import std
.concurrency
;
26 import arsd
.simpledisplay
;
32 import iv
.nanovg
.oui
.blendish
;
45 // ////////////////////////////////////////////////////////////////////////// //
46 BookText
loadBook (string fname
) {
47 __gshared
bool eliteShipsLoaded
= false;
50 //import core.memory : GC;
51 fname
= fname
.expandTilde
.absolutePath
;
52 //writeln("loading '", fname, "'...");
54 auto stt
= MonoTime
.currTime
;
55 auto book
= new BookText(fname
);
58 { import std
.stdio
; writeln("loaded: '", fname
, "' in ", (MonoTime
.currTime
-stt
).total
!"msecs", " milliseconds"); }
63 foreach (string line
; VFile(buildPath(RcDir
, ".lastfile")).byLineCopy
) {
67 while (lines
.length
&& lines
[$-1].isComment
) {
68 comments
~= lines
[$-1];
69 lines
= lines
[0..$-1];
73 } catch (Exception
) {}
74 try { import std
.file
; mkdirRecurse(RcDir
); } catch (Exception
) {}
75 auto fo
= VFile(buildPath(RcDir
, ".lastfile"), "w");
76 foreach (string s
; lines
) fo
.writeln(s
);
77 foreach_reverse (string s
; comments
) fo
.writeln(s
);
80 stateFileName
= buildPath(RcDir
, fname
.baseName
~".rc");
82 if (!eliteShipsLoaded
) {
84 eliteShipsLoaded
= true;
91 // ////////////////////////////////////////////////////////////////////////// //
92 public void formatBook (BookText book
, LayText lay
) {
93 assert(book
!is null);
95 void dumpTree (Tag ct
) {
97 { import std
.stdio
; writeln("tag: ", ct
.name
); }
102 void putParaContentInternal (Tag ct
) {
103 if (ct
is null) return;
105 case "strong": lay
.put(LayText
.BoldOnCh
); break;
106 case "emphasis": lay
.put(LayText
.ItalicOnCh
); break;
107 case "image": return;
108 case "style": return;
112 case "": lay
.put(ct
.text
); return;
113 default: dumpTree(ct
); throw new Exception("invalid content tag: '"~ct
.name
~"'");
115 foreach (Tag tag
; ct
.children
) putParaContentInternal(tag
);
117 case "strong": lay
.put(LayText
.BoldOnCh
); break;
118 case "emphasis": lay
.put(LayText
.ItalicOnCh
); break;
123 void putTagContents (Tag ct
) {
124 if (ct
is null) return;
125 foreach (Tag tag
; ct
.children
) putParaContentInternal(tag
);
126 lay
.put(LayText
.EndParaCh
);
129 void putParas (Tag ct
) {
130 foreach (Tag tag
; ct
.children
) {
131 if (tag
.name
.length
== 0) continue;
132 if (tag
.name
== "p") {
134 } else if (tag
.name
== "empty-line") {
135 lay
.put(LayText
.EndLineCh
);
136 } else if (tag
.name
== "text-author") {
139 throw new Exception("invalid content tag: '"~tag
.name
~"'");
144 void putAuthor (Tag ct
) {
145 foreach (Tag tag
; ct
.children
) {
146 if (tag
.name
.length
== 0) continue;
147 if (tag
.name
== "text-author") {
148 lay
.put(LayText
.EndParaCh
);
150 lay
.put(LayText
.EndParaCh
);
155 void putCite (Tag ct
) {
156 lay
.put(LayText
.CiteCh
);
159 lay
.put(LayText
.NormalTextCh
);
162 void putEpigraph (Tag ct
) {
163 lay
.put(LayText
.EpigraphCh
);
166 lay
.put(LayText
.NormalTextCh
);
169 void putStanza (Tag ct
) {
170 foreach (Tag tag
; ct
.children
) {
171 if (tag
.name
.length
== 0) continue;
172 if (tag
.name
== "text-author") continue;
173 if (tag
.name
== "title") { dumpTree(ct
); throw new Exception("titles in stanzas are not supported yet"); }
174 if (tag
.name
== "subtitle") { dumpTree(ct
); throw new Exception("subtitles in stanzas are not supported yet"); }
175 if (tag
.name
== "epigraph") { dumpTree(ct
); throw new Exception("epigraphs in poems are not supported yet"); }
176 if (tag
.name
== "date") continue;
177 if (tag
.name
== "v") { putTagContents(tag
); continue; }
179 throw new Exception("invalid content tag: '"~tag
.name
~"'");
183 void putPoem (Tag ct
) {
184 lay
.put(LayText
.PoemCh
);
185 // put epigraph (not yet)
186 // put title and subtitle
187 foreach (Tag tag
; ct
.children
) {
188 if (tag
.name
== "title") {
189 lay
.size
= lay
.size
+4;
190 scope(exit
) lay
.size
= lay
.size
-4;
192 lay
.put(LayText
.EndParaCh
);
193 } else if (tag
.name
== "subtitle") {
194 lay
.size
= lay
.size
+2;
195 scope(exit
) lay
.size
= lay
.size
-2;
197 lay
.put(LayText
.EndParaCh
);
200 foreach (Tag tag
; ct
.children
) {
201 if (tag
.name
.length
== 0) continue;
202 if (tag
.name
== "text-author") continue;
203 if (tag
.name
== "title") continue;
204 if (tag
.name
== "subtitle") continue;
205 if (tag
.name
== "epigraph") { dumpTree(ct
); throw new Exception("epigraphs in poems are not supported yet"); }
206 if (tag
.name
== "date") continue;
207 if (tag
.name
== "stanza") { putStanza(tag
); lay
.put(LayText
.EndParaCh
); continue; }
209 throw new Exception("invalid content tag: '"~tag
.name
~"'");
212 lay
.put(LayText
.NormalTextCh
);
215 void putSection (Tag sc
) {
216 bool sectionRegistered
= false;
218 void registerSection (Tag tag
) {
219 import std
.conv
: to
;
220 if (sectionRegistered
) return;
221 sectionRegistered
= true;
222 string text
= tag
.textContent
.xstrip
;
223 lay
.sections
~= lay
.curWordIndex
;
225 while (text
.length
) {
226 char ch
= text
.ptr
[0];
228 if (ch
<= ' ' || ch
== 127) {
229 if (name
.length
== 0) continue;
230 if (ch
!= '\n') ch
= ' ';
232 if (name
[$-1] > ' ') name
~= "\n...";
235 if (name
[$-1] > ' ') name
~= ' ';
242 if (name
.length
== 0) name
= "* * *";
243 lay
.sectionNames
~= name
.to
!dstring
;
246 foreach (Tag tag
; sc
.children
) {
247 if (tag
.name
.length
== 0) continue;
248 if (tag
.name
== "title") {
249 lay
.size
= lay
.size
+4;
250 scope(exit
) lay
.size
= lay
.size
-4;
251 lay
.put(LayText
.TitleCh
);
252 registerSection(tag
);
254 lay
.put(LayText
.NormalTextCh
);
255 } else if (tag
.name
== "subtitle") {
256 lay
.size
= lay
.size
+2;
257 scope(exit
) lay
.size
= lay
.size
-2;
258 lay
.put(LayText
.SubtitleCh
);
259 registerSection(tag
);
261 lay
.put(LayText
.NormalTextCh
);
262 } else if (tag
.name
== "epigraph") {
264 } else if (tag
.name
== "section") {
265 lay
.put(LayText
.EndParaCh
);
267 } else if (tag
.name
== "p") {
269 } else if (tag
.name
== "empty-line") {
270 lay
.put(LayText
.EndParaCh
);
271 } else if (tag
.name
== "cite") {
273 } else if (tag
.name
== "table") {
274 lay
.put(LayText
.CiteCh
);
275 lay
.put("TABLE SKIPPED");
276 lay
.put(LayText
.NormalTextCh
);
277 } else if (tag
.name
== "poem") {
279 } else if (tag
.name
== "image") {
280 } else if (tag
.name
== "style") {
283 throw new Exception("invalid tag in section: '"~tag
.name
~"'");
288 foreach (Tag tag
; book
.content
.children
) {
289 if (tag
.name
== "title") {
290 lay
.size
= lay
.size
+4;
291 scope(exit
) lay
.size
= lay
.size
-4;
292 lay
.put(LayText
.EndParaCh
);
293 lay
.put(LayText
.EndParaCh
);
294 lay
.put(LayText
.TitleCh
);
296 lay
.put(LayText
.NormalTextCh
);
297 } else if (tag
.name
== "subtitle") {
298 lay
.size
= lay
.size
+2;
299 scope(exit
) lay
.size
= lay
.size
-2;
300 lay
.put(LayText
.SubtitleCh
);
302 lay
.put(LayText
.NormalTextCh
);
303 } else if (tag
.name
== "epigraph") {
305 } else if (tag
.name
== "section") {
314 // ////////////////////////////////////////////////////////////////////////// //
315 private __gshared LayoutFonts laf
; // layouter font stash
318 // ////////////////////////////////////////////////////////////////////////// //
319 private void loadFmtFonts () {
320 laf
= new LayoutFonts();
322 void loadFont (string name
, string path
) {
323 int fid
= laf
.fs
.fonsAddFont(name
, path
);
324 if (fid
< 0) throw new Exception("can't load font '"~name
~"' from '"~path
~"'");
327 loadFont("text", textFontName
);
328 loadFont("texti", textiFontName
);
329 loadFont("textb", textbFontName
);
330 loadFont("textz", textzFontName
);
332 loadFont("mono", monoFontName
);
333 loadFont("monoi", monoiFontName
);
334 loadFont("monob", monobFontName
);
335 loadFont("monoz", monozFontName
);
339 // ////////////////////////////////////////////////////////////////////////// //
341 struct ReformatWork
{
342 shared(BookText
) booktext
;
347 struct ReformatWorkComplete
{
349 shared(BookText
) booktext
;
350 shared(LayText
) laytext
;
357 // ////////////////////////////////////////////////////////////////////////// //
358 void reformatThreadFn (Tid ownerTid
) {
363 int newW
= -1, newH
= -1;
370 //{ import std.stdio; writeln("reformat request received..."); }
371 book
= cast(BookText
)w
.booktext
;
372 newFileName
= w
.bookFileName
;
375 if (newW
< 1) newW
= 1;
376 if (newH
< 1) newH
= 1;
382 if (!doQuit
&& newW
> 0 && newH
> 0) {
385 //writeln("loading new book: '", newFileName, "'");
386 book
= loadBook(newFileName
);
389 int maxWidth
= newW
-4-2-BND_SCROLLBAR_WIDTH
-2;
390 if (maxWidth
< 64) maxWidth
= 64;
393 //writeln("layouting...");
394 auto stt
= MonoTime
.currTime
;
395 auto lay
= new LayText(laf
, maxWidth
);
397 lay
.size
= fsizeText
;
398 lay
.normJustify
= optJustify
;
400 book
.formatBook(lay
);
402 { import std
.stdio
; writeln("layouted in ", (MonoTime
.currTime
-stt
).total
!"msecs", " milliseconds"); }
403 auto res
= ReformatWorkComplete(newW
, newH
, cast(shared)book
, cast(shared)lay
);
405 } catch (Throwable e
) {
406 // here, we are dead and fucked (the exact order doesn't matter)
407 import core
.stdc
.stdlib
: abort
;
408 import core
.stdc
.stdio
: fprintf
, stderr
;
409 import core
.memory
: GC
;
410 import core
.thread
: thread_suspendAll
;
411 GC
.disable(); // yeah
412 thread_suspendAll(); // stop right here, you criminal scum!
413 auto s
= e
.toString();
414 fprintf(stderr
, "\n=== FATAL ===\n%.*s\n", cast(uint)s
.length
, s
.ptr
);
415 abort(); // die, you bitch!
419 send(ownerTid
, QuitWork());