removed some debug code
[xreader.git] / xreaderfmt.d
blobbe8e5a4ca65db89cbf1b8f9d96d7495ce03f8bf0
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/>.
17 module xreaderfmt;
19 import core.time;
21 import std.concurrency;
23 import iv.strex;
25 import arsd.simpledisplay;
26 import arsd.color;
27 import arsd.png;
28 import arsd.jpeg;
30 import iv.nanovg;
31 import iv.nanovg.oui.blendish;
32 import iv.vfs;
33 import iv.vfs.io;
35 import booktext;
37 import xreadercfg;
38 import xlayouter;
41 // ////////////////////////////////////////////////////////////////////////// //
42 BookText loadBook (string fname) {
43 __gshared bool eliteShipsLoaded = false;
45 import std.path;
46 //import core.memory : GC;
47 fname = fname.expandTilde.absolutePath;
48 //writeln("loading '", fname, "'...");
49 //GC.disable();
50 auto stt = MonoTime.currTime;
51 auto book = new BookText(fname);
52 //GC.enable();
53 //GC.collect();
54 { import std.stdio; writeln("loaded: '", fname, "' in ", (MonoTime.currTime-stt).total!"msecs", " milliseconds"); }
56 string[] lines;
57 string[] comments;
58 try {
59 foreach (string line; VFile(buildPath(RcDir, ".lastfile")).byLineCopy) {
60 if (line != fname) {
61 lines ~= line;
62 } else {
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);
74 fo.writeln(fname);
76 stateFileName = buildPath(RcDir, fname.baseName~".rc");
78 if (!eliteShipsLoaded) {
79 loadEliteShips();
80 eliteShipsLoaded = true;
83 return book;
87 // ////////////////////////////////////////////////////////////////////////// //
88 class BookMetadata {
89 static struct Anc {
90 dstring name;
91 uint wordidx; // first word
93 Anc[] sections;
94 Anc[] hrefs; // `name` is dest
95 Anc[] ids;
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");
112 } else {
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;
125 fixFont(lay);
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;
136 fixFont(lay);
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;
147 fixFont(lay);
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;
159 fixFont(lay);
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;
171 fixFont(lay);
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;
183 fixFont(lay);
186 void dumpTree (Tag ct) {
187 while (ct !is null) {
188 { import std.stdio; writeln("tag: ", ct.name); }
189 ct = ct.parent;
193 void badTag (Tag tag, string msg=null) {
194 import std.string : format, indexOf;
195 assert(tag !is null);
196 dumpTree(tag);
197 if (msg.length == 0) msg = "invalid tag: '%s'";
198 if (msg.indexOf("%s") >= 0) {
199 throw new Exception(msg.format(tag.name));
200 } else {
201 throw new Exception(msg);
205 void saveTagId (Tag tag) {
206 if (tag.id.length) {
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;
216 saveTagId(ct);
217 switch (ct.name) {
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;
222 case "a":
223 if (ct.href.length) {
224 import std.conv : to;
225 meta.hrefs ~= BookMetadata.Anc(ct.href.to!dstring, lay.nextWordIndex);
226 ++underc;
227 lay.fontStyle.underline = true;
228 lay.fontStyle.color = colorTextHref.asUint;
230 break;
231 case "": lay.put(ct.text); return;
232 default: badTag(ct);
234 fixFont(lay);
235 foreach (Tag tag; ct.children) putParaContentInternal(tag, boldc, italicc, underc);
236 switch (ct.name) {
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;
240 default:
242 fixFont(lay);
245 void putTagContents (Tag ct) {
246 if (ct is null) return;
247 saveTagId(ct);
248 foreach (Tag tag; ct.children) putParaContentInternal(tag);
249 lay.endPara();
252 void putParas (Tag ct) {
253 saveTagId(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") {}
259 else badTag(tag);
263 void putAuthor (Tag ct) {
264 saveTagId(ct);
265 foreach (Tag tag; ct.children) {
266 if (tag.name.length == 0) continue;
267 if (tag.name == "text-author") {
268 putTagContents(tag);
269 lay.endPara();
274 void putCite (Tag ct) {
275 lay.pushStyles();
276 scope(exit) lay.popStyles;
277 setCiteStyle(lay);
278 putParas(ct);
279 putAuthor(ct);
282 void putEpigraph (Tag ct) {
283 lay.pushStyles();
284 scope(exit) lay.popStyles;
285 setEpigraphStyle(lay);
286 putParas(ct);
287 putAuthor(ct);
290 void putStanza (Tag ct) {
291 saveTagId(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; }
300 badTag(tag);
304 void putPoem (Tag ct) {
305 saveTagId(ct);
306 lay.pushStyles();
307 scope(exit) lay.popStyles;
308 setPoemStyle(lay);
309 // put epigraph (not yet)
310 // put title and subtitle
311 foreach (Tag tag; ct.children) {
312 if (tag.name == "title") {
313 lay.pushStyles();
314 scope(exit) lay.popStyles;
315 putParas(tag);
316 lay.endPara(); // space
317 } else if (tag.name == "subtitle") {
318 lay.pushStyles();
319 scope(exit) lay.popStyles;
320 putParas(tag);
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; }
332 badTag(tag);
334 putAuthor(ct);
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;
346 string name;
347 while (text.length) {
348 char ch = text.ptr[0];
349 text = text[1..$];
350 if (ch <= ' ' || ch == 127) {
351 if (name.length == 0) continue;
352 if (ch != '\n') ch = ' ';
353 if (ch == '\n') {
354 if (name[$-1] > ' ') name ~= "\n...";
355 text = text.xstrip;
356 } else {
357 if (name[$-1] > ' ') name ~= ' ';
359 } else {
360 name ~= ch;
363 name = xstrip(name);
364 if (name.length == 0) name = "* * *";
365 meta.sections ~= BookMetadata.Anc(name.to!dstring, lay.nextWordIndex);
368 saveTagId(sc);
369 foreach (Tag tag; sc.children) {
370 if (tag.name.length == 0) continue;
371 if (tag.name == "title") {
372 lay.pushStyles();
373 scope(exit) lay.popStyles;
374 setTitleStyle(lay);
375 registerSection(tag);
376 putParas(tag);
377 } else if (tag.name == "subtitle") {
378 lay.pushStyles();
379 scope(exit) lay.popStyles;
380 setSubtitleStyle(lay);
381 registerSection(tag);
382 putParas(tag);
383 } else if (tag.name == "epigraph") {
384 lay.pushStyles();
385 scope(exit) lay.popStyles;
386 setEpigraphStyle(lay);
387 putEpigraph(tag);
388 } else if (tag.name == "section") {
389 putSection(tag);
390 } else if (tag.name == "p") {
391 putTagContents(tag);
392 } else if (tag.name == "empty-line") {
393 lay.put(LayText.EndParaCh);
394 } else if (tag.name == "cite") {
395 putCite(tag);
396 } else if (tag.name == "table") {
397 lay.pushStyles();
398 scope(exit) lay.popStyles;
399 lay.fontStyle.fontsize += 8;
400 lay.lineStyle.setCenter;
401 lay.put("TABLE SKIPPED");
402 lay.endPara();
403 } else if (tag.name == "poem") {
404 putPoem(tag);
405 } else if (tag.name == "image") {
406 } else if (tag.name == "style") {
407 } else {
408 badTag(tag);
413 foreach (Tag tag; book.content.children) {
414 if (tag.name == "title") {
415 lay.pushStyles();
416 scope(exit) lay.popStyles;
417 setTitleStyle(lay);
418 lay.endPara();
419 putParas(tag);
420 lay.endPara();
421 } else if (tag.name == "subtitle") {
422 lay.pushStyles();
423 scope(exit) lay.popStyles;
424 setTitleStyle(lay);
425 putParas(tag);
426 lay.endPara();
427 } else if (tag.name == "epigraph") {
428 putEpigraph(tag);
429 } else if (tag.name == "section") {
430 lay.pushStyles();
431 scope(exit) lay.popStyles;
432 setNormalStyle(lay);
433 putSection(tag);
437 lay.finalize();
439 return meta;
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 // ////////////////////////////////////////////////////////////////////////// //
464 // messages
465 struct ReformatWork {
466 shared(BookText) booktext;
467 string bookFileName;
468 int w, h;
471 struct ReformatWorkComplete {
472 int w, h;
473 shared(BookText) booktext;
474 shared(LayText) laytext;
475 shared(BookMetadata) meta;
478 struct QuitWork {
482 // ////////////////////////////////////////////////////////////////////////// //
483 void reformatThreadFn (Tid ownerTid) {
484 bool doQuit = false;
485 loadFmtFonts();
486 BookText book;
487 string newFileName;
488 int newW = -1, newH = -1;
489 while (!doQuit) {
490 book = null;
491 newFileName = null;
492 newW = newH = -1;
493 receive(
494 (ReformatWork w) {
495 //{ import std.stdio; writeln("reformat request received..."); }
496 book = cast(BookText)w.booktext;
497 newFileName = w.bookFileName;
498 newW = w.w;
499 newH = w.h;
500 if (newW < 1) newW = 1;
501 if (newH < 1) newH = 1;
503 (QuitWork w) {
504 doQuit = true;
507 if (!doQuit && newW > 0 && newH > 0) {
508 try {
509 if (book is null) {
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;
517 // layout text
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);
527 send(ownerTid, res);
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());