it seems to work again
[xreader.git] / xreaderfmt.d
blobd74cbd407201608d21c187d38a8f539e1a448b45
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 public void formatBook (BookText book, LayText lay) {
89 assert(book !is null);
91 static void fixFont (LayText lay) {
92 if (lay.fontStyle.italic) {
93 if (lay.fontStyle.bold) lay.fontStyle.fontface = lay.fontFace("textz");
94 else lay.fontStyle.fontface = lay.fontFace("texti");
95 } else if (lay.fontStyle.bold) {
96 if (lay.fontStyle.italic) lay.fontStyle.fontface = lay.fontFace("textz");
97 else lay.fontStyle.fontface = lay.fontFace("textb");
98 } else {
99 lay.fontStyle.fontface = lay.fontFace("text");
103 static void setNormalStyle (LayText lay) {
104 lay.fontStyle.flags = 0;
105 lay.fontStyle.fontsize = fsizeText;
106 lay.lineStyle.leftpad = 0;
107 lay.lineStyle.rightpad = 0;
108 lay.lineStyle.paraIndent = 3;
109 if (optJustify) lay.lineStyle.setJustify; else lay.lineStyle.setLeft;
110 fixFont(lay);
113 static void setTitleStyle (LayText lay) {
114 lay.fontStyle.flags = 0;
115 lay.fontStyle.fontsize = fsizeText+4;
116 lay.lineStyle.leftpad = 0;
117 lay.lineStyle.rightpad = 0;
118 lay.lineStyle.paraIndent = 0;
119 lay.lineStyle.setCenter;
120 fixFont(lay);
123 static void setSubtitleStyle (LayText lay) {
124 lay.fontStyle.flags = 0;
125 lay.fontStyle.fontsize = fsizeText+2;
126 lay.lineStyle.leftpad = 0;
127 lay.lineStyle.rightpad = 0;
128 lay.lineStyle.paraIndent = 0;
129 lay.lineStyle.setCenter;
130 fixFont(lay);
133 static void setCiteStyle (LayText lay) {
134 lay.fontStyle.flags = 0;
135 lay.fontStyle.fontsize = fsizeText;
136 lay.fontStyle.italic = true;
137 lay.lineStyle.leftpad = fsizeText*4;
138 lay.lineStyle.rightpad = fsizeText*4;
139 lay.lineStyle.paraIndent = 3;
140 if (optJustify) lay.lineStyle.setJustify; else lay.lineStyle.setLeft;
141 fixFont(lay);
144 static void setEpigraphStyle (LayText lay) {
145 lay.fontStyle.flags = 0;
146 lay.fontStyle.fontsize = fsizeText;
147 lay.fontStyle.italic = true;
148 lay.lineStyle.leftpad = lay.width/2;
149 lay.lineStyle.rightpad = 0;
150 lay.lineStyle.paraIndent = 0;
151 lay.lineStyle.setRight;
152 fixFont(lay);
155 static void setPoemStyle (LayText lay) {
156 lay.fontStyle.flags = 0;
157 lay.fontStyle.fontsize = fsizeText;
158 lay.fontStyle.italic = true;
159 lay.lineStyle.leftpad = fsizeText*4;
160 lay.lineStyle.rightpad = 0;
161 lay.lineStyle.paraIndent = 0;
162 lay.lineStyle.setLeft;
163 fixFont(lay);
166 void dumpTree (Tag ct) {
167 while (ct !is null) {
168 { import std.stdio; writeln("tag: ", ct.name); }
169 ct = ct.parent;
173 void badTag (Tag tag, string msg=null) {
174 import std.string : format, indexOf;
175 assert(tag !is null);
176 dumpTree(tag);
177 if (msg.length == 0) msg = "invalid tag: '%s'";
178 if (msg.indexOf("%s") >= 0) {
179 throw new Exception(msg.format(tag.name));
180 } else {
181 throw new Exception(msg);
185 void putParaContentInternal (Tag ct, int boldc=0, int italicc=0, int underc=0) {
186 if (ct is null) return;
187 switch (ct.name) {
188 case "strong": ++boldc; lay.fontStyle.bold = true; break;
189 case "emphasis": ++italicc; lay.fontStyle.italic = true; break;
190 case "image": return;
191 case "style": return;
192 case "a": ++underc; lay.fontStyle.underline = true; break;
193 case "": lay.put(ct.text); return;
194 default: badTag(ct);
196 fixFont(lay);
197 foreach (Tag tag; ct.children) putParaContentInternal(tag, boldc, italicc, underc);
198 switch (ct.name) {
199 case "strong": if (--boldc == 0) lay.fontStyle.bold = false; break;
200 case "emphasis": if (--italicc == 0) lay.fontStyle.italic = false; break;
201 case "a": if (--underc == 0) lay.fontStyle.underline = false; break;
202 default:
204 fixFont(lay);
207 void putTagContents (Tag ct) {
208 if (ct is null) return;
209 foreach (Tag tag; ct.children) putParaContentInternal(tag);
210 lay.endPara();
213 void putParas (Tag ct) {
214 foreach (Tag tag; ct.children) {
215 if (tag.name.length == 0) continue;
216 if (tag.name == "p") putTagContents(tag);
217 else if (tag.name == "empty-line") lay.endLine();
218 else if (tag.name == "text-author") {}
219 else badTag(tag);
223 void putAuthor (Tag ct) {
224 foreach (Tag tag; ct.children) {
225 if (tag.name.length == 0) continue;
226 if (tag.name == "text-author") {
227 putTagContents(tag);
228 lay.endPara();
233 void putCite (Tag ct) {
234 lay.pushStyles();
235 scope(exit) lay.popStyles;
236 setCiteStyle(lay);
237 putParas(ct);
238 putAuthor(ct);
241 void putEpigraph (Tag ct) {
242 lay.pushStyles();
243 scope(exit) lay.popStyles;
244 setEpigraphStyle(lay);
245 putParas(ct);
246 putAuthor(ct);
249 void putStanza (Tag ct) {
250 foreach (Tag tag; ct.children) {
251 if (tag.name.length == 0) continue;
252 if (tag.name == "text-author") continue;
253 if (tag.name == "title") badTag(tag, "titles in stanzas are not supported yet");
254 if (tag.name == "subtitle") badTag(tag, "subtitles in stanzas are not supported yet");
255 if (tag.name == "epigraph") badTag(tag, "epigraphs in poems are not supported yet");
256 if (tag.name == "date") continue;
257 if (tag.name == "v") { putTagContents(tag); continue; }
258 badTag(tag);
262 void putPoem (Tag ct) {
263 lay.pushStyles();
264 scope(exit) lay.popStyles;
265 setPoemStyle(lay);
266 // put epigraph (not yet)
267 // put title and subtitle
268 foreach (Tag tag; ct.children) {
269 if (tag.name == "title") {
270 lay.pushStyles();
271 scope(exit) lay.popStyles;
272 putParas(tag);
273 lay.endPara(); // space
274 } else if (tag.name == "subtitle") {
275 lay.pushStyles();
276 scope(exit) lay.popStyles;
277 putParas(tag);
278 lay.endPara(); // space
281 foreach (Tag tag; ct.children) {
282 if (tag.name.length == 0) continue;
283 if (tag.name == "text-author") continue;
284 if (tag.name == "title") continue;
285 if (tag.name == "subtitle") continue;
286 if (tag.name == "epigraph") badTag(tag, "epigraphs in poems are not supported yet");
287 if (tag.name == "date") continue;
288 if (tag.name == "stanza") { putStanza(tag); lay.put(LayText.EndParaCh); continue; }
289 badTag(tag);
291 putAuthor(ct);
294 void putSection (Tag sc) {
295 bool sectionRegistered = false;
297 void registerSection (Tag tag) {
298 /+!!!
299 import std.conv : to;
300 if (sectionRegistered) return;
301 sectionRegistered = true;
302 string text = tag.textContent.xstrip;
303 lay.sections ~= lay.curWordIndex;
304 string name;
305 while (text.length) {
306 char ch = text.ptr[0];
307 text = text[1..$];
308 if (ch <= ' ' || ch == 127) {
309 if (name.length == 0) continue;
310 if (ch != '\n') ch = ' ';
311 if (ch == '\n') {
312 if (name[$-1] > ' ') name ~= "\n...";
313 text = text.xstrip;
314 } else {
315 if (name[$-1] > ' ') name ~= ' ';
317 } else {
318 name ~= ch;
321 name = xstrip(name);
322 if (name.length == 0) name = "* * *";
323 lay.sectionNames ~= name.to!dstring;
327 foreach (Tag tag; sc.children) {
328 if (tag.name.length == 0) continue;
329 if (tag.name == "title") {
330 lay.pushStyles();
331 scope(exit) lay.popStyles;
332 setTitleStyle(lay);
333 registerSection(tag);
334 putParas(tag);
335 } else if (tag.name == "subtitle") {
336 lay.pushStyles();
337 scope(exit) lay.popStyles;
338 setSubtitleStyle(lay);
339 registerSection(tag);
340 putParas(tag);
341 } else if (tag.name == "epigraph") {
342 lay.pushStyles();
343 scope(exit) lay.popStyles;
344 setEpigraphStyle(lay);
345 putEpigraph(tag);
346 } else if (tag.name == "section") {
347 putSection(tag);
348 } else if (tag.name == "p") {
349 putTagContents(tag);
350 } else if (tag.name == "empty-line") {
351 lay.put(LayText.EndParaCh);
352 } else if (tag.name == "cite") {
353 putCite(tag);
354 } else if (tag.name == "table") {
355 lay.pushStyles();
356 scope(exit) lay.popStyles;
357 lay.fontStyle.fontsize += 8;
358 lay.lineStyle.setCenter;
359 lay.put("TABLE SKIPPED");
360 lay.endPara();
361 } else if (tag.name == "poem") {
362 putPoem(tag);
363 } else if (tag.name == "image") {
364 } else if (tag.name == "style") {
365 } else {
366 badTag(tag);
371 foreach (Tag tag; book.content.children) {
372 if (tag.name == "title") {
373 lay.pushStyles();
374 scope(exit) lay.popStyles;
375 setTitleStyle(lay);
376 lay.endPara();
377 putParas(tag);
378 lay.endPara();
379 } else if (tag.name == "subtitle") {
380 lay.pushStyles();
381 scope(exit) lay.popStyles;
382 setTitleStyle(lay);
383 putParas(tag);
384 lay.endPara();
385 } else if (tag.name == "epigraph") {
386 putEpigraph(tag);
387 } else if (tag.name == "section") {
388 lay.pushStyles();
389 scope(exit) lay.popStyles;
390 setNormalStyle(lay);
391 putSection(tag);
395 lay.finalize();
399 // ////////////////////////////////////////////////////////////////////////// //
400 private __gshared LayFontStash laf; // layouter font stash
403 // ////////////////////////////////////////////////////////////////////////// //
404 private void loadFmtFonts () {
405 laf = new LayFontStash();
407 laf.addFont("text", textFontName);
408 laf.addFont("texti", textiFontName);
409 laf.addFont("textb", textbFontName);
410 laf.addFont("textz", textzFontName);
412 laf.addFont("mono", monoFontName);
413 laf.addFont("monoi", monoiFontName);
414 laf.addFont("monob", monobFontName);
415 laf.addFont("monoz", monozFontName);
419 // ////////////////////////////////////////////////////////////////////////// //
420 // messages
421 struct ReformatWork {
422 shared(BookText) booktext;
423 string bookFileName;
424 int w, h;
427 struct ReformatWorkComplete {
428 int w, h;
429 shared(BookText) booktext;
430 shared(LayText) laytext;
433 struct QuitWork {
437 // ////////////////////////////////////////////////////////////////////////// //
438 void reformatThreadFn (Tid ownerTid) {
439 bool doQuit = false;
440 loadFmtFonts();
441 BookText book;
442 string newFileName;
443 int newW = -1, newH = -1;
444 while (!doQuit) {
445 book = null;
446 newFileName = null;
447 newW = newH = -1;
448 receive(
449 (ReformatWork w) {
450 //{ import std.stdio; writeln("reformat request received..."); }
451 book = cast(BookText)w.booktext;
452 newFileName = w.bookFileName;
453 newW = w.w;
454 newH = w.h;
455 if (newW < 1) newW = 1;
456 if (newH < 1) newH = 1;
458 (QuitWork w) {
459 doQuit = true;
462 if (!doQuit && newW > 0 && newH > 0) {
463 try {
464 if (book is null) {
465 //writeln("loading new book: '", newFileName, "'");
466 book = loadBook(newFileName);
469 int maxWidth = newW-4-2-BND_SCROLLBAR_WIDTH-2;
470 if (maxWidth < 64) maxWidth = 64;
472 // layout text
473 //writeln("layouting...");
474 auto stt = MonoTime.currTime;
475 auto lay = new LayText(laf, maxWidth);
477 book.formatBook(lay);
479 { import std.stdio; writeln("layouted in ", (MonoTime.currTime-stt).total!"msecs", " milliseconds"); }
480 auto res = ReformatWorkComplete(newW, newH, cast(shared)book, cast(shared)lay);
481 send(ownerTid, res);
482 } catch (Throwable e) {
483 // here, we are dead and fucked (the exact order doesn't matter)
484 import core.stdc.stdlib : abort;
485 import core.stdc.stdio : fprintf, stderr;
486 import core.memory : GC;
487 import core.thread : thread_suspendAll;
488 GC.disable(); // yeah
489 thread_suspendAll(); // stop right here, you criminal scum!
490 auto s = e.toString();
491 fprintf(stderr, "\n=== FATAL ===\n%.*s\n", cast(uint)s.length, s.ptr);
492 abort(); // die, you bitch!
496 send(ownerTid, QuitWork());