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
;
30 import iv
.nanovg
.oui
.blendish
;
40 // ////////////////////////////////////////////////////////////////////////// //
41 BookText
loadBook (string fname
) {
42 __gshared
bool eliteShipsLoaded
= false;
45 //import core.memory : GC;
46 fname
= fname
.expandTilde
.absolutePath
;
47 //conwriteln("loading '", fname, "'...");
49 auto stt
= MonoTime
.currTime
;
50 auto book
= new BookText(fname
);
53 { conwriteln("loaded: '", fname
, "' in ", (MonoTime
.currTime
-stt
).total
!"msecs", " milliseconds"); }
58 foreach (string line
; VFile(buildPath(RcDir
, ".lastfile")).byLineCopy
) {
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
);
75 stateFileName
= buildPath(RcDir
, fname
.baseName
~".rc");
77 if (!eliteShipsLoaded
) {
79 eliteShipsLoaded
= true;
86 // ////////////////////////////////////////////////////////////////////////// //
87 private struct ImageInfo
{
88 enum MaxIdleFrames
= 5;
94 private __gshared ImageInfo
[] nvgImageIds
;
97 void releaseImages (NVGContext vg
) {
98 if (nvgImageIds
.length
) {
99 foreach (ref ImageInfo nfo
; nvgImageIds
) {
101 if (++nfo
.framesNotUsed
> ImageInfo
.MaxIdleFrames
) {
102 //conwriteln("freeing image with id ", nfo.iid);
103 vg
.deleteImage(nfo
.iid
);
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;
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);
131 class BookImage
: LayObject
{
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
) {
146 scope(exit
) vg
.restore();
148 int iid
= vg
.registerImage(img
);
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
);
158 // ////////////////////////////////////////////////////////////////////////// //
162 uint wordidx
; // first word
165 Anc
[uint] hrefs
; // `name` is dest
170 // ////////////////////////////////////////////////////////////////////////// //
171 final class FBFormatter
{
183 void formatBook (BookText abook
, int maxWidth
) {
184 assert(abook
!is null);
186 lay
= new LayText(laf
, maxWidth
);
187 lay
.fontStyle
.color
= colorText
.asUint
;
189 meta
= new BookMetadata();
192 scope(exit
) book
= null;
201 void dumpTree (Tag ct
) {
202 while (ct
!is null) {
203 conwriteln("tag: ", ct
.name
);
208 void badTag (Tag tag
, string msg
=null) {
209 import std
.string
: format
, indexOf
;
210 assert(tag
!is null);
212 if (msg
.length
== 0) msg
= "invalid tag: '%s'";
213 if (msg
.indexOf("%s") >= 0) {
214 throw new Exception(msg
.format(tag
.name
));
216 throw new Exception(msg
);
220 void saveTagId (Tag tag
) {
222 import std
.conv
: to
;
223 meta
.ids
~= BookMetadata
.Anc(tag
.id
.to
!dstring
, lay
.nextWordIndex
);
227 void registerSection (Tag tag
) {
228 import std
.conv
: to
;
229 string text
= tag
.textContent
.xstrip
;
230 //lay.sections ~= lay.curWordIndex;
232 while (text
.length
) {
233 char ch
= text
.ptr
[0];
235 if (ch
<= ' ' || ch
== 127) {
236 if (name
.length
== 0) continue;
237 if (ch
!= '\n') ch
= ' ';
239 if (name
[$-1] > ' ') name
~= "\n...";
242 if (name
[$-1] > ' ') name
~= ' ';
249 if (name
.length
== 0) name
= "* * *";
250 meta
.sections
~= BookMetadata
.Anc(name
.to
!dstring
, lay
.nextWordIndex
);
253 void putImage (Tag tag
) {
254 if (tag
is null || tag
.href
.length
< 2 || tag
.href
[0] != '#') return;
255 string iid
= tag
.href
[1..$];
256 //conwriteln("searching for image with href '", iid, "' (", book.images.length, ")");
257 foreach (immutable idx
, ref BookText
.Image img
; book
.images
) {
259 //conwriteln("image '", img.id, "' found");
260 lay
.putObject(new BookImage(book
, cast(uint)idx
));
266 void putParasXX(bool allowBad
=false) (Tag tag
) {
267 if (tag
is null || tag
.name
.length
== 0) return;
281 putTagContents(tag
, 1);
285 lay
.put(LayText
.EndParaCh
);
288 //FIXME:??? somehow this is invisible
290 scope(exit
) lay
.popStyles
;
292 if (tag
.text
.length
) { putParaContentInternal(tag
); lay
.endPara(); } else putTagContents(tag
);
296 putTagContents(tag
); // some idiotic files has this
300 putParaContentInternal(tag
); // some idiotic files has this
304 scope(exit
) lay
.popStyles
;
305 //auto olpad = lay.lineStyle.leftpad;
306 auto orpad
= lay
.lineStyle
.rightpad
;
308 //lay.lineStyle.leftpad = lay.lineStyle.leftpad+olpad;
309 lay
.lineStyle
.rightpad
= lay
.lineStyle
.rightpad
+orpad
;
310 putEpigraph(tag
, false);
314 scope(exit
) lay
.popStyles
;
315 lay
.fontStyle
.italic
= true;
316 putEpigraph(tag
, false);
319 static if (allowBad
) {
327 void putParaContentInternal (Tag ct
, int boldc
=0, int italicc
=0, int underc
=0) {
328 if (ct
is null) return;
329 auto c
= lay
.fontStyle
.color
;
330 scope(exit
) lay
.fontStyle
.color
= c
;
331 bool popStyles
= false;
332 bool doFixFont
= true;
334 if (popStyles
) lay
.popStyles
;
339 case "strong": ++boldc
; lay
.fontStyle
.bold
= true; break;
340 case "emphasis": ++italicc
; lay
.fontStyle
.italic
= true; break;
341 case "strikethrough": lay
.pushStyles(); popStyles
= true; lay
.fontStyle
.strike
= true; break;
342 case "image": putImage(ct
); return;
343 case "style": return;
345 if (ct
.href
.length
) {
346 import std
.conv
: to
;
347 //conwriteln("href found: '", ct.href, "' (", lay.nextWordIndex, ")");
348 meta
.hrefs
[lay
.nextWordIndex
] = BookMetadata
.Anc(ct
.href
.to
!dstring
, lay
.nextWordIndex
);
350 lay
.fontStyle
.href
= true;
351 lay
.fontStyle
.underline
= true;
352 lay
.fontStyle
.color
= colorTextHref
.asUint
;
355 case "sub": break; // some idiotic files has this
356 case "sup": break; // some idiotic files has this
358 putTagContents(ct
, boldc
, italicc
, underc
);
379 lay
.fontStyle
.bold
= true;
380 lay
.lineStyle
.setCenter
;
386 lay
.put(LayText
.EndParaCh
);
395 if (doFixFont
) fixFont();
396 foreach (Tag tag
; ct
.children
) putParaContentInternal(tag
, boldc
, italicc
, underc
);
398 case "strong": case "site": case "annotation": if (--boldc
== 0) lay
.fontStyle
.bold
= false; break;
399 case "emphasis": if (--italicc
== 0) lay
.fontStyle
.italic
= false; break;
400 case "a": if (--underc
== 0) lay
.fontStyle
.underline
= false; lay
.fontStyle
.href
= false; break;
405 bool onlyImagePara (Tag ct
) {
406 if (ct
is null) return false;
408 foreach (Tag tag
; ct
.children
) {
409 if (tag
.name
.length
== 0) {
411 if (tag
.text
.xstrip
.length
!= 0) return false;
414 if (count
!= 0 || tag
.name
!= "image") return false;
420 void putTagContents
/*(bool xdump=false)*/ (Tag ct
, int boldc
=0, int italicc
=0, int underc
=0) {
421 if (ct
is null) return;
423 if (onlyImagePara(ct
)) {
425 scope(exit
) lay
.popStyles
;
426 lay
.lineStyle
.setCenter
;
427 foreach (Tag tag
; ct
.children
) putParaContentInternal(tag
, boldc
, italicc
, underc
);
429 //if (ct.name == "subtitle") conwriteln("text: [", ct.children[0].text, "]");
430 foreach (Tag tag
; ct
.children
) {
431 //static if (xdump) conwriteln("text: [", tag.text, "]");
432 putParaContentInternal(tag
, boldc
, italicc
, underc
);
438 void putParas (Tag ct
) {
440 foreach (Tag tag
; ct
.children
) {
445 void putAuthor (Tag ct
) {
447 foreach (Tag tag
; ct
.children
) {
448 if (tag
.name
.length
== 0) continue;
449 if (tag
.name
== "text-author") {
456 void putCite (Tag ct
) {
458 scope(exit
) lay
.popStyles
;
464 void putEpigraph (Tag ct
, bool setstyle
=true) {
465 if (setstyle
) lay
.pushStyles();
466 scope(exit
) if (setstyle
) lay
.popStyles
;
467 if (setstyle
) setEpigraphStyle();
472 void putStanza (Tag ct
) {
474 foreach (Tag tag
; ct
.children
) {
475 if (tag
.name
.length
== 0) continue;
476 if (tag
.name
== "text-author") continue;
477 if (tag
.name
== "title") badTag(tag
, "titles in stanzas are not supported yet");
478 if (tag
.name
== "subtitle") badTag(tag
, "subtitles in stanzas are not supported yet");
479 if (tag
.name
== "epigraph") badTag(tag
, "epigraphs in poems are not supported yet");
480 if (tag
.name
== "date") continue;
481 if (tag
.name
== "v") { putTagContents(tag
); continue; }
486 void putPoem (Tag ct
) {
489 scope(exit
) lay
.popStyles
;
491 // put epigraph (not yet)
492 // put title and subtitle
493 foreach (Tag tag
; ct
.children
) {
494 if (tag
.name
== "title") {
496 scope(exit
) lay
.popStyles
;
497 if (!hasSections
) registerSection(tag
);
499 lay
.endPara(); // space
500 } else if (tag
.name
== "subtitle") {
502 scope(exit
) lay
.popStyles
;
504 //lay.endPara(); // space
508 foreach (Tag tag
; ct
.children
) {
509 if (tag
.name
.length
== 0) continue;
510 if (tag
.name
== "text-author") continue;
511 if (tag
.name
== "title") continue;
512 if (tag
.name
== "subtitle") continue;
513 if (tag
.name
== "epigraph") badTag(tag
, "epigraphs in poems are not supported yet");
514 if (tag
.name
== "date") continue;
515 if (tag
.name
== "stanza") { putStanza(tag
); lay
.put(LayText
.EndParaCh
); continue; }
521 void putSection (Tag sc
) {
522 bool sectionRegistered
= false;
526 foreach (Tag tag
; sc
.children
) {
527 if (tag
.name
.length
== 0) continue;
528 if (tag
.name
== "title") {
530 scope(exit
) lay
.popStyles
;
532 if (!sectionRegistered
) { sectionRegistered
= true; registerSection(tag
); }
534 } else if (tag
.name
== "subtitle") {
536 scope(exit
) lay
.popStyles
;
538 if (!sectionRegistered
) { sectionRegistered
= true; registerSection(tag
); }
540 } else if (tag
.name
== "epigraph") {
542 } else if (tag
.name
== "section") {
544 scope(exit
) lay
.popStyles
;
547 } else if (tag
.name
== "p") {
549 } else if (tag
.name
== "image") {
551 scope(exit
) lay
.popStyles
;
552 lay
.lineStyle
.leftpad
= 0;
553 lay
.lineStyle
.rightpad
= 0;
554 lay
.lineStyle
.paraIndent
= 0;
555 lay
.lineStyle
.setCenter
;
558 } else if (tag
.name
== "empty-line") {
560 } else if (tag
.name
== "cite") {
562 } else if (tag
.name
== "table") {
564 scope(exit
) lay
.popStyles
;
565 lay
.fontStyle
.fontsize
+= 8;
566 lay
.lineStyle
.setCenter
;
567 lay
.put("TABLE SKIPPED");
569 } else if (tag
.name
== "poem") {
571 } else if (tag
.name
== "image") {
572 } else if (tag
.name
== "style") {
573 } else if (tag
.name
== "site" || tag
.name
== "annotation") {
582 foreach (Tag tag
; book
.content
.children
) {
583 if (tag
.name
== "title") {
585 scope(exit
) lay
.popStyles
;
588 if (!hasSections
) registerSection(tag
);
591 } else if (tag
.name
== "subtitle") {
593 scope(exit
) lay
.popStyles
;
597 } else if (tag
.name
== "epigraph") {
599 } else if (tag
.name
== "section") {
601 scope(exit
) lay
.popStyles
;
604 } else if (tag
.name
== "image") {
606 } else /*if (tag.name == "empty-line") {
608 } else if (tag.name == "p") {
610 scope(exit) lay.popStyles;
617 scope(exit
) lay
.popStyles
;
619 putParasXX
!true(tag
);
626 if (lay
.fontStyle
.italic
) {
627 if (lay
.fontStyle
.bold
) lay
.fontStyle
.fontface
= lay
.fontFaceId("textz");
628 else lay
.fontStyle
.fontface
= lay
.fontFaceId("texti");
629 } else if (lay
.fontStyle
.bold
) {
630 if (lay
.fontStyle
.italic
) lay
.fontStyle
.fontface
= lay
.fontFaceId("textz");
631 else lay
.fontStyle
.fontface
= lay
.fontFaceId("textb");
633 lay
.fontStyle
.fontface
= lay
.fontFaceId("text");
637 void setNormalStyle () {
638 lay
.fontStyle
.resetAttrs
;
639 lay
.fontStyle
.fontsize
= fsizeText
;
640 lay
.fontStyle
.color
= colorText
.asUint
;
641 lay
.lineStyle
.leftpad
= 0;
642 lay
.lineStyle
.rightpad
= 0;
643 lay
.lineStyle
.paraIndent
= 3;
644 if (optJustify
) lay
.lineStyle
.setJustify
; else lay
.lineStyle
.setLeft
;
648 void setTitleStyle () {
649 lay
.fontStyle
.resetAttrs
;
650 lay
.fontStyle
.bold
= true;
651 lay
.fontStyle
.fontsize
= fsizeText
+4;
652 lay
.fontStyle
.color
= colorText
.asUint
;
653 lay
.lineStyle
.leftpad
= 0;
654 lay
.lineStyle
.rightpad
= 0;
655 lay
.lineStyle
.paraIndent
= 0;
656 lay
.lineStyle
.setCenter
;
660 void setSubtitleStyle () {
661 lay
.fontStyle
.resetAttrs
;
662 lay
.fontStyle
.bold
= true;
663 lay
.fontStyle
.fontsize
= fsizeText
+2;
664 lay
.fontStyle
.color
= colorText
.asUint
;
665 lay
.lineStyle
.leftpad
= 0;
666 lay
.lineStyle
.rightpad
= 0;
667 lay
.lineStyle
.paraIndent
= 0;
668 lay
.lineStyle
.setCenter
;
672 void setCiteStyle () {
673 lay
.fontStyle
.resetAttrs
;
674 lay
.fontStyle
.italic
= true;
675 lay
.fontStyle
.fontsize
= fsizeText
;
676 lay
.fontStyle
.color
= colorText
.asUint
;
677 lay
.lineStyle
.leftpad
= fsizeText
*3;
678 lay
.lineStyle
.rightpad
= fsizeText
*3;
679 lay
.lineStyle
.paraIndent
= 3;
680 if (optJustify
) lay
.lineStyle
.setJustify
; else lay
.lineStyle
.setLeft
;
684 void setEpigraphStyle () {
685 lay
.fontStyle
.resetAttrs
;
686 lay
.fontStyle
.italic
= true;
687 lay
.fontStyle
.fontsize
= fsizeText
;
688 lay
.fontStyle
.color
= colorText
.asUint
;
689 lay
.lineStyle
.leftpad
= lay
.width
/3;
690 lay
.lineStyle
.rightpad
= 0;
691 lay
.lineStyle
.paraIndent
= 0;
692 lay
.lineStyle
.setRight
;
696 void setPoemStyle () {
697 lay
.fontStyle
.resetAttrs
;
698 lay
.fontStyle
.italic
= true;
699 lay
.fontStyle
.fontsize
= fsizeText
;
700 lay
.fontStyle
.color
= colorText
.asUint
;
701 lay
.lineStyle
.leftpad
= fsizeText
*3;
702 lay
.lineStyle
.rightpad
= 0;
703 lay
.lineStyle
.paraIndent
= 0;
704 lay
.lineStyle
.setLeft
;
708 void setCodeStyle () {
709 lay
.fontStyle
.resetAttrs
;
710 lay
.fontStyle
.italic
= false;
711 lay
.fontStyle
.fontsize
= fsizeText
;
712 lay
.fontStyle
.color
= colorText
.asUint
;
713 lay
.lineStyle
.leftpad
= fsizeText
*3;
714 lay
.lineStyle
.rightpad
= fsizeText
*3;
715 lay
.lineStyle
.paraIndent
= 0;
716 lay
.lineStyle
.setLeft
;
717 lay
.fontStyle
.fontface
= lay
.fontFaceId("mono");
722 // ////////////////////////////////////////////////////////////////////////// //
723 private __gshared LayFontStash laf
; // layouter font stash
726 // ////////////////////////////////////////////////////////////////////////// //
727 private void loadFmtFonts () {
728 laf
= new LayFontStash();
730 laf
.addFont("text", textFontName
);
731 laf
.addFont("texti", textiFontName
);
732 laf
.addFont("textb", textbFontName
);
733 laf
.addFont("textz", textzFontName
);
735 laf
.addFont("mono", monoFontName
);
736 laf
.addFont("monoi", monoiFontName
);
737 laf
.addFont("monob", monobFontName
);
738 laf
.addFont("monoz", monozFontName
);
742 // ////////////////////////////////////////////////////////////////////////// //
744 struct ReformatWork
{
745 shared(BookText
) booktext
;
750 struct ReformatWorkComplete
{
752 shared(BookText
) booktext
;
753 shared(LayText
) laytext
;
754 shared(BookMetadata
) meta
;
761 // ////////////////////////////////////////////////////////////////////////// //
762 void reformatThreadFn (Tid ownerTid
) {
767 int newW
= -1, newH
= -1;
774 //{ conwriteln("reformat request received..."); }
775 book
= cast(BookText
)w
.booktext
;
776 newFileName
= w
.bookFileName
;
779 if (newW
< 1) newW
= 1;
780 if (newH
< 1) newH
= 1;
786 if (!doQuit
&& newW
> 0 && newH
> 0) {
789 //conwriteln("loading new book: '", newFileName, "'");
790 book
= loadBook(newFileName
);
793 int maxWidth
= newW
-4-2-BND_SCROLLBAR_WIDTH
-2;
794 if (maxWidth
< 64) maxWidth
= 64;
797 //conwriteln("layouting...");
798 auto fmtr
= new FBFormatter();
800 auto stt
= MonoTime
.currTime
;
802 auto lay = new LayText(laf, maxWidth);
803 lay.fontStyle.color = colorText.asUint;
804 auto meta = book.formatBook(lay);
806 fmtr
.formatBook(book
, maxWidth
);
807 auto ett
= MonoTime
.currTime
-stt
;
809 auto meta
= fmtr
.meta
;
812 conwriteln("layouted in ", ett
.total
!"msecs", " milliseconds");
813 auto res
= ReformatWorkComplete(newW
, newH
, cast(shared)book
, cast(shared)lay
, cast(shared)meta
);
815 } catch (Throwable e
) {
816 // here, we are dead and fucked (the exact order doesn't matter)
817 import core
.stdc
.stdlib
: abort
;
818 import core
.stdc
.stdio
: fprintf
, stderr
;
819 import core
.memory
: GC
;
820 import core
.thread
: thread_suspendAll
;
821 GC
.disable(); // yeah
822 thread_suspendAll(); // stop right here, you criminal scum!
823 auto s
= e
.toString();
824 fprintf(stderr
, "\n=== FATAL ===\n%.*s\n", cast(uint)s
.length
, s
.ptr
);
825 abort(); // die, you bitch!
829 send(ownerTid
, QuitWork());