replaced formatting function with formatting class
[xreader.git] / xreaderfmt.d
blob9c2ab5ecd36342e0d0b5c2d4c171a9812641045d
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.image;
28 import iv.cmdcon;
29 import iv.nanovg;
30 import iv.nanovg.oui.blendish;
31 import iv.vfs;
32 import iv.vfs.io;
34 import booktext;
36 import xreadercfg;
37 import xlayouter;
40 // ////////////////////////////////////////////////////////////////////////// //
41 BookText loadBook (string fname) {
42 __gshared bool eliteShipsLoaded = false;
44 import std.path;
45 //import core.memory : GC;
46 fname = fname.expandTilde.absolutePath;
47 //conwriteln("loading '", fname, "'...");
48 //GC.disable();
49 auto stt = MonoTime.currTime;
50 auto book = new BookText(fname);
51 //GC.enable();
52 //GC.collect();
53 { conwriteln("loaded: '", fname, "' in ", (MonoTime.currTime-stt).total!"msecs", " milliseconds"); }
55 string[] lines;
56 string[] comments;
57 try {
58 foreach (string line; VFile(buildPath(RcDir, ".lastfile")).byLineCopy) {
59 if (line != fname) {
60 lines ~= line;
61 } else {
62 while (lines.length && lines[$-1].isComment) {
63 comments ~= lines[$-1];
64 lines = lines[0..$-1];
68 } catch (Exception) {}
69 try { import std.file; mkdirRecurse(RcDir); } catch (Exception) {}
70 auto fo = VFile(buildPath(RcDir, ".lastfile"), "w");
71 foreach (string s; lines) fo.writeln(s);
72 foreach_reverse (string s; comments) fo.writeln(s);
73 fo.writeln(fname);
75 stateFileName = buildPath(RcDir, fname.baseName~".rc");
77 if (!eliteShipsLoaded) {
78 loadEliteShips();
79 eliteShipsLoaded = true;
82 return book;
86 // ////////////////////////////////////////////////////////////////////////// //
87 private struct ImageInfo {
88 enum MaxIdleFrames = 5;
89 int iid;
90 TrueColorImage img;
91 int framesNotUsed;
94 private __gshared ImageInfo[] nvgImageIds;
97 void releaseImages (NVGContext vg) {
98 if (nvgImageIds.length) {
99 foreach (ref ImageInfo nfo; nvgImageIds) {
100 if (nfo.iid >= 0) {
101 if (++nfo.framesNotUsed > ImageInfo.MaxIdleFrames) {
102 //conwriteln("freeing image with id ", nfo.iid);
103 vg.deleteImage(nfo.iid);
104 nfo.iid = -1;
105 nfo.img = null;
113 private int registerImage (NVGContext vg, TrueColorImage img) {
114 if (vg is null || img is null) return -1;
115 uint freeIdx = uint.max;
116 foreach (immutable idx, ref ImageInfo nfo; nvgImageIds) {
117 if (nfo.img is img) {
118 assert(nfo.iid >= 0);
119 nfo.framesNotUsed = 0;
120 return nfo.iid;
122 if (freeIdx != uint.max && nfo.iid < 0) freeIdx = cast(uint)idx;
124 int iid = vg.createImageFromMemoryImage(img); //vg.createImageRGBA(img.width, img.height, img.imageData.bytes[], NVGImageFlags.None);
125 if (iid < 0) return -1;
126 nvgImageIds ~= ImageInfo(iid, img, 0);
127 return iid;
131 class BookImage : LayObject {
132 BookText book;
133 uint imageidx;
134 TrueColorImage img;
135 this (BookText abook, uint aidx) { book = abook; imageidx = aidx; img = book.images[aidx].img; }
136 override int width () { return img.width; }
137 override int spacewidth () { return 0; }
138 override int height () { return img.height; }
139 override int ascent () { return img.height; }
140 override int descent () { return 0; }
141 override bool canbreak () { return true; }
142 override bool spaced () { return false; }
143 override void draw (NVGContext vg, float x, float y) {
144 y -= img.height;
145 vg.save();
146 scope(exit) vg.restore();
147 vg.beginPath();
148 int iid = vg.registerImage(img);
149 if (iid < 0) return;
150 vg.fillPaint(vg.imagePattern(x, y, img.width, img.height, 0, iid));
151 //vg.globalAlpha(0.5);
152 vg.rect(x, y, img.width, img.height);
153 vg.fill();
158 // ////////////////////////////////////////////////////////////////////////// //
159 class BookMetadata {
160 static struct Anc {
161 dstring name;
162 uint wordidx; // first word
164 Anc[] sections;
165 Anc[uint] hrefs; // `name` is dest
166 Anc[] ids;
170 // ////////////////////////////////////////////////////////////////////////// //
171 final class FBFormatter {
172 private:
173 BookText book;
175 public:
176 LayText lay;
177 BookMetadata meta;
179 public:
180 this () {}
182 void formatBook (BookText abook, int maxWidth) {
183 assert(abook !is null);
185 lay = new LayText(laf, maxWidth);
186 lay.fontStyle.color = colorText.asUint;
188 meta = new BookMetadata();
190 book = abook;
191 scope(exit) book = null;
193 putMain();
195 lay.finalize();
198 private:
199 void dumpTree (Tag ct) {
200 while (ct !is null) {
201 conwriteln("tag: ", ct.name);
202 ct = ct.parent;
206 void badTag (Tag tag, string msg=null) {
207 import std.string : format, indexOf;
208 assert(tag !is null);
209 dumpTree(tag);
210 if (msg.length == 0) msg = "invalid tag: '%s'";
211 if (msg.indexOf("%s") >= 0) {
212 throw new Exception(msg.format(tag.name));
213 } else {
214 throw new Exception(msg);
218 void saveTagId (Tag tag) {
219 if (tag.id.length) {
220 import std.conv : to;
221 meta.ids ~= BookMetadata.Anc(tag.id.to!dstring, lay.nextWordIndex);
225 void putImage (Tag tag) {
226 if (tag is null || tag.href.length < 2 || tag.href[0] != '#') return;
227 string iid = tag.href[1..$];
228 //conwriteln("searching for image with href '", iid, "' (", book.images.length, ")");
229 foreach (immutable idx, ref BookText.Image img; book.images) {
230 if (iid == img.id) {
231 //conwriteln("image '", img.id, "' found");
232 lay.putObject(new BookImage(book, cast(uint)idx));
233 return;
238 void putParasXX(bool allowBad=false) (Tag tag) {
239 if (tag is null || tag.name.length == 0) return;
240 switch (tag.name) {
241 case "p":
242 putTagContents(tag);
243 break;
244 case "empty-line":
245 lay.endLine();
246 break;
247 case "br":
248 lay.endPara();
249 break;
250 case "text-author":
251 break;
252 case "strong":
253 putTagContents(tag, 1);
254 break;
255 case "poem":
256 putPoem(tag);
257 lay.put(LayText.EndParaCh);
258 break;
259 case "subtitle":
260 //FIXME:??? somehow this is invisible
261 lay.pushStyles();
262 scope(exit) lay.popStyles;
263 setSubtitleStyle();
264 if (tag.text.length) { putParaContentInternal(tag); lay.endPara(); } else putTagContents(tag);
265 break;
266 case "sub":
267 case "sup":
268 putTagContents(tag); // some idiotic files has this
269 break;
270 case "site":
271 case "annotation":
272 putParaContentInternal(tag); // some idiotic files has this
273 break;
274 case "epigraph":
275 lay.pushStyles();
276 scope(exit) lay.popStyles;
277 //auto olpad = lay.lineStyle.leftpad;
278 auto orpad = lay.lineStyle.rightpad;
279 setEpigraphStyle();
280 //lay.lineStyle.leftpad = lay.lineStyle.leftpad+olpad;
281 lay.lineStyle.rightpad = lay.lineStyle.rightpad+orpad;
282 putEpigraph(tag, false);
283 break;
284 case "emphasis":
285 lay.pushStyles();
286 scope(exit) lay.popStyles;
287 lay.fontStyle.italic = true;
288 putEpigraph(tag, false);
289 break;
290 default:
291 static if (allowBad) {
292 break;
293 } else {
294 badTag(tag);
299 void putParaContentInternal (Tag ct, int boldc=0, int italicc=0, int underc=0) {
300 if (ct is null) return;
301 auto c = lay.fontStyle.color;
302 scope(exit) lay.fontStyle.color = c;
303 bool popStyles = false;
304 bool doFixFont = true;
305 scope(exit) {
306 if (popStyles) lay.popStyles;
307 fixFont();
309 saveTagId(ct);
310 switch (ct.name) {
311 case "strong": ++boldc; lay.fontStyle.bold = true; break;
312 case "emphasis": ++italicc; lay.fontStyle.italic = true; break;
313 case "strikethrough": lay.pushStyles(); popStyles = true; lay.fontStyle.strike = true; break;
314 case "image": putImage(ct); return;
315 case "style": return;
316 case "a":
317 if (ct.href.length) {
318 import std.conv : to;
319 //conwriteln("href found: '", ct.href, "' (", lay.nextWordIndex, ")");
320 meta.hrefs[lay.nextWordIndex] = BookMetadata.Anc(ct.href.to!dstring, lay.nextWordIndex);
321 ++underc;
322 lay.fontStyle.href = true;
323 lay.fontStyle.underline = true;
324 lay.fontStyle.color = colorTextHref.asUint;
326 break;
327 case "sub": break; // some idiotic files has this
328 case "sup": break; // some idiotic files has this
329 case "p":
330 putTagContents(ct, boldc, italicc, underc);
331 fixFont();
332 return;
333 case "br":
334 //lay.endLine();
335 lay.endPara();
336 return;
337 case "empty-line":
338 lay.endLine();
339 return;
340 case "code":
341 lay.pushStyles();
342 popStyles = true;
343 setCodeStyle();
344 doFixFont = false;
345 break;
346 case "site":
347 case "annotation":
348 lay.pushStyles();
349 popStyles = true;
350 ++boldc;
351 lay.fontStyle.bold = true;
352 lay.lineStyle.setCenter;
353 break;
354 case "poem":
355 lay.pushStyles();
356 popStyles = true;
357 putPoem(ct);
358 lay.put(LayText.EndParaCh);
359 return;
360 case "":
361 lay.put(ct.text);
362 return;
363 default:
364 badTag(ct);
365 break;
367 if (doFixFont) fixFont();
368 foreach (Tag tag; ct.children) putParaContentInternal(tag, boldc, italicc, underc);
369 switch (ct.name) {
370 case "strong": case "site": case "annotation": if (--boldc == 0) lay.fontStyle.bold = false; break;
371 case "emphasis": if (--italicc == 0) lay.fontStyle.italic = false; break;
372 case "a": if (--underc == 0) lay.fontStyle.underline = false; lay.fontStyle.href = false; break;
373 default:
377 bool onlyImagePara (Tag ct) {
378 if (ct is null) return false;
379 int count;
380 foreach (Tag tag; ct.children) {
381 if (tag.name.length == 0) {
382 // text
383 if (tag.text.xstrip.length != 0) return false;
384 continue;
386 if (count != 0 || tag.name != "image") return false;
387 ++count;
389 return (count == 1);
392 void putTagContents/*(bool xdump=false)*/ (Tag ct, int boldc=0, int italicc=0, int underc=0) {
393 if (ct is null) return;
394 saveTagId(ct);
395 if (onlyImagePara(ct)) {
396 lay.pushStyles();
397 scope(exit) lay.popStyles;
398 lay.lineStyle.setCenter;
399 foreach (Tag tag; ct.children) putParaContentInternal(tag, boldc, italicc, underc);
400 } else {
401 //if (ct.name == "subtitle") conwriteln("text: [", ct.children[0].text, "]");
402 foreach (Tag tag; ct.children) {
403 //static if (xdump) conwriteln("text: [", tag.text, "]");
404 putParaContentInternal(tag, boldc, italicc, underc);
407 lay.endPara();
410 void putParas (Tag ct) {
411 saveTagId(ct);
412 foreach (Tag tag; ct.children) {
413 putParasXX(tag);
417 void putAuthor (Tag ct) {
418 saveTagId(ct);
419 foreach (Tag tag; ct.children) {
420 if (tag.name.length == 0) continue;
421 if (tag.name == "text-author") {
422 putTagContents(tag);
423 lay.endPara();
428 void putCite (Tag ct) {
429 lay.pushStyles();
430 scope(exit) lay.popStyles;
431 setCiteStyle();
432 putParas(ct);
433 putAuthor(ct);
436 void putEpigraph (Tag ct, bool setstyle=true) {
437 if (setstyle) lay.pushStyles();
438 scope(exit) if (setstyle) lay.popStyles;
439 if (setstyle) setEpigraphStyle();
440 putParas(ct);
441 putAuthor(ct);
444 void putStanza (Tag ct) {
445 saveTagId(ct);
446 foreach (Tag tag; ct.children) {
447 if (tag.name.length == 0) continue;
448 if (tag.name == "text-author") continue;
449 if (tag.name == "title") badTag(tag, "titles in stanzas are not supported yet");
450 if (tag.name == "subtitle") badTag(tag, "subtitles in stanzas are not supported yet");
451 if (tag.name == "epigraph") badTag(tag, "epigraphs in poems are not supported yet");
452 if (tag.name == "date") continue;
453 if (tag.name == "v") { putTagContents(tag); continue; }
454 badTag(tag);
458 void putPoem (Tag ct) {
459 saveTagId(ct);
460 lay.pushStyles();
461 scope(exit) lay.popStyles;
462 setPoemStyle();
463 // put epigraph (not yet)
464 // put title and subtitle
465 foreach (Tag tag; ct.children) {
466 if (tag.name == "title") {
467 lay.pushStyles();
468 scope(exit) lay.popStyles;
469 putParas(tag);
470 lay.endPara(); // space
471 } else if (tag.name == "subtitle") {
472 lay.pushStyles();
473 scope(exit) lay.popStyles;
474 putParas(tag);
475 //lay.endPara(); // space
478 lay.endPara;
479 foreach (Tag tag; ct.children) {
480 if (tag.name.length == 0) continue;
481 if (tag.name == "text-author") continue;
482 if (tag.name == "title") continue;
483 if (tag.name == "subtitle") continue;
484 if (tag.name == "epigraph") badTag(tag, "epigraphs in poems are not supported yet");
485 if (tag.name == "date") continue;
486 if (tag.name == "stanza") { putStanza(tag); lay.put(LayText.EndParaCh); continue; }
487 badTag(tag);
489 putAuthor(ct);
492 void putSection (Tag sc) {
493 bool sectionRegistered = false;
495 void registerSection (Tag tag) {
496 import std.conv : to;
497 if (sectionRegistered) return;
498 sectionRegistered = true;
499 string text = tag.textContent.xstrip;
500 //lay.sections ~= lay.curWordIndex;
501 string name;
502 while (text.length) {
503 char ch = text.ptr[0];
504 text = text[1..$];
505 if (ch <= ' ' || ch == 127) {
506 if (name.length == 0) continue;
507 if (ch != '\n') ch = ' ';
508 if (ch == '\n') {
509 if (name[$-1] > ' ') name ~= "\n...";
510 text = text.xstrip;
511 } else {
512 if (name[$-1] > ' ') name ~= ' ';
514 } else {
515 name ~= ch;
518 name = xstrip(name);
519 if (name.length == 0) name = "* * *";
520 meta.sections ~= BookMetadata.Anc(name.to!dstring, lay.nextWordIndex);
523 saveTagId(sc);
524 foreach (Tag tag; sc.children) {
525 if (tag.name.length == 0) continue;
526 if (tag.name == "title") {
527 lay.pushStyles();
528 scope(exit) lay.popStyles;
529 setTitleStyle();
530 registerSection(tag);
531 putParas(tag);
532 } else if (tag.name == "subtitle") {
533 lay.pushStyles();
534 scope(exit) lay.popStyles;
535 setSubtitleStyle();
536 registerSection(tag);
537 putParas(tag);
538 } else if (tag.name == "epigraph") {
539 putEpigraph(tag);
540 } else if (tag.name == "section") {
541 lay.pushStyles();
542 scope(exit) lay.popStyles;
543 setNormalStyle();
544 putSection(tag);
545 } else if (tag.name == "p") {
546 putTagContents(tag);
547 } else if (tag.name == "image") {
548 lay.pushStyles();
549 scope(exit) lay.popStyles;
550 lay.lineStyle.leftpad = 0;
551 lay.lineStyle.rightpad = 0;
552 lay.lineStyle.paraIndent = 0;
553 lay.lineStyle.setCenter;
554 putImage(tag);
555 lay.endPara();
556 } else if (tag.name == "empty-line") {
557 lay.endPara();
558 } else if (tag.name == "cite") {
559 putCite(tag);
560 } else if (tag.name == "table") {
561 lay.pushStyles();
562 scope(exit) lay.popStyles;
563 lay.fontStyle.fontsize += 8;
564 lay.lineStyle.setCenter;
565 lay.put("TABLE SKIPPED");
566 lay.endPara();
567 } else if (tag.name == "poem") {
568 putPoem(tag);
569 } else if (tag.name == "image") {
570 } else if (tag.name == "style") {
571 } else if (tag.name == "site" || tag.name == "annotation") {
572 } else {
573 badTag(tag);
576 lay.endPara;
579 void putMain () {
580 foreach (Tag tag; book.content.children) {
581 if (tag.name == "title") {
582 lay.pushStyles();
583 scope(exit) lay.popStyles;
584 setTitleStyle();
585 lay.endPara();
586 putParas(tag);
587 lay.endPara();
588 } else if (tag.name == "subtitle") {
589 lay.pushStyles();
590 scope(exit) lay.popStyles;
591 setTitleStyle();
592 putParas(tag);
593 lay.endPara();
594 } else if (tag.name == "epigraph") {
595 putEpigraph(tag);
596 } else if (tag.name == "section") {
597 lay.pushStyles();
598 scope(exit) lay.popStyles;
599 setNormalStyle();
600 putSection(tag);
601 } else if (tag.name == "image") {
602 putImage(tag);
603 } else /*if (tag.name == "empty-line") {
604 lay.endLine();
605 } else if (tag.name == "p") {
606 lay.pushStyles();
607 scope(exit) lay.popStyles;
608 setNormalStyle(lay);
609 putParas(tag);
610 lay.endPara();
611 putTagContents(tag);
612 }*/{
613 lay.pushStyles();
614 scope(exit) lay.popStyles;
615 setNormalStyle();
616 putParasXX!true(tag);
621 private:
622 void fixFont () {
623 if (lay.fontStyle.italic) {
624 if (lay.fontStyle.bold) lay.fontStyle.fontface = lay.fontFaceId("textz");
625 else lay.fontStyle.fontface = lay.fontFaceId("texti");
626 } else if (lay.fontStyle.bold) {
627 if (lay.fontStyle.italic) lay.fontStyle.fontface = lay.fontFaceId("textz");
628 else lay.fontStyle.fontface = lay.fontFaceId("textb");
629 } else {
630 lay.fontStyle.fontface = lay.fontFaceId("text");
634 void setNormalStyle () {
635 lay.fontStyle.resetAttrs;
636 lay.fontStyle.fontsize = fsizeText;
637 lay.fontStyle.color = colorText.asUint;
638 lay.lineStyle.leftpad = 0;
639 lay.lineStyle.rightpad = 0;
640 lay.lineStyle.paraIndent = 3;
641 if (optJustify) lay.lineStyle.setJustify; else lay.lineStyle.setLeft;
642 fixFont();
645 void setTitleStyle () {
646 lay.fontStyle.resetAttrs;
647 lay.fontStyle.bold = true;
648 lay.fontStyle.fontsize = fsizeText+4;
649 lay.fontStyle.color = colorText.asUint;
650 lay.lineStyle.leftpad = 0;
651 lay.lineStyle.rightpad = 0;
652 lay.lineStyle.paraIndent = 0;
653 lay.lineStyle.setCenter;
654 fixFont();
657 void setSubtitleStyle () {
658 lay.fontStyle.resetAttrs;
659 lay.fontStyle.bold = true;
660 lay.fontStyle.fontsize = fsizeText+2;
661 lay.fontStyle.color = colorText.asUint;
662 lay.lineStyle.leftpad = 0;
663 lay.lineStyle.rightpad = 0;
664 lay.lineStyle.paraIndent = 0;
665 lay.lineStyle.setCenter;
666 fixFont();
669 void setCiteStyle () {
670 lay.fontStyle.resetAttrs;
671 lay.fontStyle.italic = true;
672 lay.fontStyle.fontsize = fsizeText;
673 lay.fontStyle.color = colorText.asUint;
674 lay.lineStyle.leftpad = fsizeText*3;
675 lay.lineStyle.rightpad = fsizeText*3;
676 lay.lineStyle.paraIndent = 3;
677 if (optJustify) lay.lineStyle.setJustify; else lay.lineStyle.setLeft;
678 fixFont();
681 void setEpigraphStyle () {
682 lay.fontStyle.resetAttrs;
683 lay.fontStyle.italic = true;
684 lay.fontStyle.fontsize = fsizeText;
685 lay.fontStyle.color = colorText.asUint;
686 lay.lineStyle.leftpad = lay.width/3;
687 lay.lineStyle.rightpad = 0;
688 lay.lineStyle.paraIndent = 0;
689 lay.lineStyle.setRight;
690 fixFont();
693 void setPoemStyle () {
694 lay.fontStyle.resetAttrs;
695 lay.fontStyle.italic = true;
696 lay.fontStyle.fontsize = fsizeText;
697 lay.fontStyle.color = colorText.asUint;
698 lay.lineStyle.leftpad = fsizeText*3;
699 lay.lineStyle.rightpad = 0;
700 lay.lineStyle.paraIndent = 0;
701 lay.lineStyle.setLeft;
702 fixFont();
705 void setCodeStyle () {
706 lay.fontStyle.resetAttrs;
707 lay.fontStyle.italic = false;
708 lay.fontStyle.fontsize = fsizeText;
709 lay.fontStyle.color = colorText.asUint;
710 lay.lineStyle.leftpad = fsizeText*3;
711 lay.lineStyle.rightpad = fsizeText*3;
712 lay.lineStyle.paraIndent = 0;
713 lay.lineStyle.setLeft;
714 lay.fontStyle.fontface = lay.fontFaceId("mono");
719 // ////////////////////////////////////////////////////////////////////////// //
720 private __gshared LayFontStash laf; // layouter font stash
723 // ////////////////////////////////////////////////////////////////////////// //
724 private void loadFmtFonts () {
725 laf = new LayFontStash();
727 laf.addFont("text", textFontName);
728 laf.addFont("texti", textiFontName);
729 laf.addFont("textb", textbFontName);
730 laf.addFont("textz", textzFontName);
732 laf.addFont("mono", monoFontName);
733 laf.addFont("monoi", monoiFontName);
734 laf.addFont("monob", monobFontName);
735 laf.addFont("monoz", monozFontName);
739 // ////////////////////////////////////////////////////////////////////////// //
740 // messages
741 struct ReformatWork {
742 shared(BookText) booktext;
743 string bookFileName;
744 int w, h;
747 struct ReformatWorkComplete {
748 int w, h;
749 shared(BookText) booktext;
750 shared(LayText) laytext;
751 shared(BookMetadata) meta;
754 struct QuitWork {
758 // ////////////////////////////////////////////////////////////////////////// //
759 void reformatThreadFn (Tid ownerTid) {
760 bool doQuit = false;
761 loadFmtFonts();
762 BookText book;
763 string newFileName;
764 int newW = -1, newH = -1;
765 while (!doQuit) {
766 book = null;
767 newFileName = null;
768 newW = newH = -1;
769 receive(
770 (ReformatWork w) {
771 //{ conwriteln("reformat request received..."); }
772 book = cast(BookText)w.booktext;
773 newFileName = w.bookFileName;
774 newW = w.w;
775 newH = w.h;
776 if (newW < 1) newW = 1;
777 if (newH < 1) newH = 1;
779 (QuitWork w) {
780 doQuit = true;
783 if (!doQuit && newW > 0 && newH > 0) {
784 try {
785 if (book is null) {
786 //conwriteln("loading new book: '", newFileName, "'");
787 book = loadBook(newFileName);
790 int maxWidth = newW-4-2-BND_SCROLLBAR_WIDTH-2;
791 if (maxWidth < 64) maxWidth = 64;
793 // layout text
794 //conwriteln("layouting...");
795 auto fmtr = new FBFormatter();
797 auto stt = MonoTime.currTime;
799 auto lay = new LayText(laf, maxWidth);
800 lay.fontStyle.color = colorText.asUint;
801 auto meta = book.formatBook(lay);
803 fmtr.formatBook(book, maxWidth);
804 auto ett = MonoTime.currTime-stt;
806 auto meta = fmtr.meta;
807 auto lay = fmtr.lay;
809 conwriteln("layouted in ", ett.total!"msecs", " milliseconds");
810 auto res = ReformatWorkComplete(newW, newH, cast(shared)book, cast(shared)lay, cast(shared)meta);
811 send(ownerTid, res);
812 } catch (Throwable e) {
813 // here, we are dead and fucked (the exact order doesn't matter)
814 import core.stdc.stdlib : abort;
815 import core.stdc.stdio : fprintf, stderr;
816 import core.memory : GC;
817 import core.thread : thread_suspendAll;
818 GC.disable(); // yeah
819 thread_suspendAll(); // stop right here, you criminal scum!
820 auto s = e.toString();
821 fprintf(stderr, "\n=== FATAL ===\n%.*s\n", cast(uint)s.length, s.ptr);
822 abort(); // die, you bitch!
826 send(ownerTid, QuitWork());