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
;
31 import iv
.nanovg
.textlayouter
;
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 auto oldFixFontDG
= layFixFontDG
;
187 scope(exit
) layFixFontDG
= oldFixFontDG
;
191 lay
= new LayTextC(laf
, maxWidth
);
192 lay
.fontStyle
.color
= colorText
.asUint
;
194 layFixFontDG
= &fixFont
;
196 meta
= new BookMetadata();
199 scope(exit
) book
= null;
208 void dumpTree (Tag ct
) {
209 while (ct
!is null) {
210 conwriteln("tag: ", ct
.name
);
215 void badTag (Tag tag
, string msg
=null) {
216 import std
.string
: format
, indexOf
;
217 assert(tag
!is null);
219 if (msg
.length
== 0) msg
= "invalid tag: '%s'";
220 if (msg
.indexOf("%s") >= 0) {
221 throw new Exception(msg
.format(tag
.name
));
223 throw new Exception(msg
);
227 void saveTagId (Tag tag
) {
229 import std
.conv
: to
;
230 meta
.ids
~= BookMetadata
.Anc(tag
.id
.to
!dstring
, lay
.nextWordIndex
);
234 void registerSection (Tag tag
) {
235 import std
.conv
: to
;
236 string text
= tag
.textContent
.xstrip
;
237 //lay.sections ~= lay.curWordIndex;
239 while (text
.length
) {
240 char ch
= text
.ptr
[0];
242 if (ch
<= ' ' || ch
== 127) {
243 if (name
.length
== 0) continue;
244 if (ch
!= '\n') ch
= ' ';
246 if (name
[$-1] > ' ') name
~= "\n...";
249 if (name
[$-1] > ' ') name
~= ' ';
256 if (name
.length
== 0) name
= "* * *";
257 meta
.sections
~= BookMetadata
.Anc(name
.to
!dstring
, lay
.nextWordIndex
);
260 void putImage (Tag tag
) {
261 if (tag
is null || tag
.href
.length
< 2 || tag
.href
[0] != '#') return;
262 string iid
= tag
.href
[1..$];
263 //conwriteln("searching for image with href '", iid, "' (", book.images.length, ")");
264 foreach (immutable idx
, ref BookText
.Image img
; book
.images
) {
266 //conwriteln("image '", img.id, "' found");
267 lay
.putObject(new BookImage(book
, cast(uint)idx
));
273 void putOneTag(bool allowBad
=false, bool allowText
=true) (Tag tag
) {
274 if (tag
is null) return;
275 if (tag
.name
.length
== 0) {
276 static if (allowText
) {
290 scope(exit
) lay
.popStyles
;
291 lay
.fontStyle
.bold
= true;
296 scope(exit
) lay
.popStyles
;
297 lay
.fontStyle
.italic
= true;
301 case "strikethrough":
303 scope(exit
) lay
.popStyles
;
304 lay
.fontStyle
.strike
= true;
318 scope(exit
) lay
.popStyles
;
320 lay
.lineStyle
.setCenter
;
330 if (tag
.href
.length
) {
331 import std
.conv
: to
;
332 //conwriteln("href found: '", ct.href, "' (", lay.nextWordIndex, ")");
333 meta
.hrefs
[lay
.nextWordIndex
] = BookMetadata
.Anc(tag
.href
.to
!dstring
, lay
.nextWordIndex
);
335 auto c
= lay
.fontStyle
.color
;
336 scope(exit
) { lay
.popStyles
; lay
.fontStyle
.color
= c
; }
337 lay
.fontStyle
.href
= true;
338 lay
.fontStyle
.underline
= true;
339 lay
.fontStyle
.color
= colorTextHref
.asUint
;
347 case "sub": // some idiotic files has this
348 case "sup": // some idiotic files has this
354 scope(exit
) lay
.popStyles
;
362 scope(exit
) lay
.popStyles
;
372 case "v": // for stanzas
377 case "date": // for poem
380 scope(exit
) lay
.popStyles
;
381 lay
.fontStyle
.bold
= true;
382 lay
.fontStyle
.italic
= true;
383 lay
.lineStyle
.setRight
;
396 scope(exit
) lay
.popStyles
;
397 lay
.fontStyle
.bold
= true;
398 lay
.lineStyle
.setCenter
;
409 scope(exit
) lay
.popStyles
;
411 registerSection(tag
);
413 if (tag
.text
.length
== 0) lay
.endPara();
418 scope(exit
) lay
.popStyles
;
421 if (tag
.text
.length
== 0) lay
.endPara();
426 scope(exit
) lay
.popStyles
;
427 //auto olpad = lay.lineStyle.leftpad;
428 auto orpad
= lay
.lineStyle
.rightpad
;
430 //lay.lineStyle.leftpad = lay.lineStyle.leftpad+olpad;
431 lay
.lineStyle
.rightpad
= lay
.lineStyle
.rightpad
+orpad
;
442 scope(exit
) lay
.popStyles
;
443 lay
.fontStyle
.fontsize
+= 8;
444 lay
.lineStyle
.setCenter
;
446 lay
.put("TABLE SKIPPED");
451 static if (allowBad
) {
459 bool onlyImagePara (Tag ct
) {
460 if (ct
is null) return false;
462 foreach (Tag tag
; ct
.children
) {
463 if (tag
.name
.length
== 0) {
465 if (tag
.text
.xstrip
.length
!= 0) return false;
468 if (count
!= 0 || tag
.name
!= "image") return false;
474 void putTagContents
/*(bool xdump=false)*/ (Tag ct
) {
475 if (ct
is null) return;
477 if (onlyImagePara(ct
)) {
479 scope(exit
) lay
.popStyles
;
481 lay
.lineStyle
.setCenter
;
482 foreach (Tag tag
; ct
.children
) putOneTag(tag
);
485 //if (ct.name == "subtitle") conwriteln("text: [", ct.children[0].text, "]");
486 foreach (Tag tag
; ct
.children
) {
487 //static if (xdump) conwriteln("text: [", tag.text, "]");
494 void putParas (Tag ct
) {
495 if (ct
is null) return;
500 void putAuthor (Tag ct
) {
502 foreach (Tag tag
; ct
.children
) {
503 if (tag
.name
.length
== 0) continue;
504 if (tag
.name
== "text-author") {
511 void putCite (Tag ct
) {
513 scope(exit
) lay
.popStyles
;
519 void putEpigraph (Tag ct
) {
524 void putPoem (Tag ct
) {
527 scope(exit
) lay
.popStyles
;
529 // put epigraph (not yet)
530 // put title and subtitle
531 foreach (Tag tag
; ct
.children
) {
532 if (tag
.name
== "title") {
534 scope(exit
) lay
.popStyles
;
535 if (!hasSections
) registerSection(tag
);
537 lay
.endPara(); // space
538 } else if (tag
.name
== "subtitle") {
540 scope(exit
) lay
.popStyles
;
542 //lay.endPara(); // space
546 foreach (Tag tag
; ct
.children
) {
547 putOneTag
!(false, false)(tag
); // skip text without tags
554 foreach (Tag tag
; book
.content
.children
) {
560 void fixFont (ref LayFontStyle st
) nothrow @safe @nogc {
563 if (st
.bold
) st
.fontface
= lay
.fontFaceId("monoz");
564 else st
.fontface
= lay
.fontFaceId("monoi");
565 } else if (st
.bold
) {
566 if (st
.italic
) st
.fontface
= lay
.fontFaceId("monoz");
567 else st
.fontface
= lay
.fontFaceId("monob");
569 st
.fontface
= lay
.fontFaceId("mono");
573 if (st
.bold
) st
.fontface
= lay
.fontFaceId("textz");
574 else st
.fontface
= lay
.fontFaceId("texti");
575 } else if (st
.bold
) {
576 if (st
.italic
) st
.fontface
= lay
.fontFaceId("textz");
577 else st
.fontface
= lay
.fontFaceId("textb");
579 st
.fontface
= lay
.fontFaceId("text");
584 void setNormalStyle () {
585 lay
.fontStyle
.resetAttrs
;
586 lay
.fontStyle
.fontsize
= fsizeText
;
587 lay
.fontStyle
.color
= colorText
.asUint
;
588 lay
.lineStyle
.leftpad
= 0;
589 lay
.lineStyle
.rightpad
= 0;
590 lay
.lineStyle
.paraIndent
= 3;
591 if (optJustify
) lay
.lineStyle
.setJustify
; else lay
.lineStyle
.setLeft
;
595 void setTitleStyle () {
596 lay
.fontStyle
.resetAttrs
;
597 lay
.fontStyle
.bold
= true;
598 lay
.fontStyle
.fontsize
= fsizeText
+4;
599 lay
.fontStyle
.color
= colorText
.asUint
;
600 lay
.lineStyle
.leftpad
= 0;
601 lay
.lineStyle
.rightpad
= 0;
602 lay
.lineStyle
.paraIndent
= 0;
603 lay
.lineStyle
.setCenter
;
607 void setSubtitleStyle () {
608 lay
.fontStyle
.resetAttrs
;
609 lay
.fontStyle
.bold
= true;
610 lay
.fontStyle
.fontsize
= fsizeText
+2;
611 lay
.fontStyle
.color
= colorText
.asUint
;
612 lay
.lineStyle
.leftpad
= 0;
613 lay
.lineStyle
.rightpad
= 0;
614 lay
.lineStyle
.paraIndent
= 0;
615 lay
.lineStyle
.setCenter
;
619 void setCiteStyle () {
620 lay
.fontStyle
.resetAttrs
;
621 lay
.fontStyle
.italic
= true;
622 lay
.fontStyle
.fontsize
= fsizeText
;
623 lay
.fontStyle
.color
= colorText
.asUint
;
624 lay
.lineStyle
.leftpad
= fsizeText
*3;
625 lay
.lineStyle
.rightpad
= fsizeText
*3;
626 lay
.lineStyle
.paraIndent
= 3;
627 if (optJustify
) lay
.lineStyle
.setJustify
; else lay
.lineStyle
.setLeft
;
631 void setEpigraphStyle () {
632 lay
.fontStyle
.resetAttrs
;
633 lay
.fontStyle
.italic
= true;
634 lay
.fontStyle
.fontsize
= fsizeText
;
635 lay
.fontStyle
.color
= colorText
.asUint
;
636 lay
.lineStyle
.leftpad
= lay
.width
/3;
637 lay
.lineStyle
.rightpad
= 0;
638 lay
.lineStyle
.paraIndent
= 0;
639 lay
.lineStyle
.setRight
;
643 void setPoemStyle () {
644 lay
.fontStyle
.resetAttrs
;
645 lay
.fontStyle
.italic
= true;
646 lay
.fontStyle
.fontsize
= fsizeText
;
647 lay
.fontStyle
.color
= colorText
.asUint
;
648 lay
.lineStyle
.leftpad
= fsizeText
*3;
649 lay
.lineStyle
.rightpad
= 0;
650 lay
.lineStyle
.paraIndent
= 0;
651 lay
.lineStyle
.setLeft
;
655 void setCodeStyle () {
656 lay
.fontStyle
.resetAttrs
;
657 lay
.fontStyle
.monospace
= true;
658 lay
.fontStyle
.italic
= false;
659 lay
.fontStyle
.fontsize
= fsizeText
;
660 lay
.fontStyle
.color
= colorText
.asUint
;
661 lay
.lineStyle
.leftpad
= fsizeText
*3;
662 lay
.lineStyle
.rightpad
= fsizeText
*3;
663 lay
.lineStyle
.paraIndent
= 0;
664 lay
.lineStyle
.setLeft
;
665 //lay.fontStyle.fontface = lay.fontFaceId("mono");
670 // ////////////////////////////////////////////////////////////////////////// //
671 private __gshared LayFontStash laf
; // layouter font stash
674 // ////////////////////////////////////////////////////////////////////////// //
675 private void loadFmtFonts (/*NVGContext nvg*/) {
677 //if (nvg !is null) { import core.stdc.stdio : printf; printf("reusing NVG context...\n"); }
678 laf
= new LayFontStash(/*nvg*/);
680 laf
.addFont("text", textFontName
);
681 laf
.addFont("texti", textiFontName
);
682 laf
.addFont("textb", textbFontName
);
683 laf
.addFont("textz", textzFontName
);
685 laf
.addFont("mono", monoFontName
);
686 laf
.addFont("monoi", monoiFontName
);
687 laf
.addFont("monob", monobFontName
);
688 laf
.addFont("monoz", monozFontName
);
693 // ////////////////////////////////////////////////////////////////////////// //
695 struct ReformatWork
{
696 shared(BookText
) booktext
;
697 //shared(NVGContext) nvg; // can be null
702 struct ReformatWorkComplete
{
704 shared(BookText
) booktext
;
705 shared(LayTextC
) laytext
;
706 shared(BookMetadata
) meta
;
713 // ////////////////////////////////////////////////////////////////////////// //
714 void reformatThreadFn (Tid ownerTid
) {
718 int newW
= -1, newH
= -1;
719 //NVGContext lastnvg = null;
726 //{ conwriteln("reformat request received..."); }
727 book
= cast(BookText
)w
.booktext
;
728 newFileName
= w
.bookFileName
;
731 if (newW
< 1) newW
= 1;
732 if (newH
< 1) newH
= 1;
733 //lastnvg = cast(NVGContext)w.nvg;
739 if (!doQuit
&& newW
> 0 && newH
> 0) {
742 //conwriteln("loading new book: '", newFileName, "'");
743 book
= loadBook(newFileName
);
746 int maxWidth
= newW
-4-2-BND_SCROLLBAR_WIDTH
-2;
747 if (maxWidth
< 64) maxWidth
= 64;
752 //conwriteln("layouting...");
753 auto fmtr
= new FBFormatter();
755 auto stt
= MonoTime
.currTime
;
757 auto lay = new LayText(laf, maxWidth);
758 lay.fontStyle.color = colorText.asUint;
759 auto meta = book.formatBook(lay);
761 fmtr
.formatBook(book
, maxWidth
);
762 auto ett
= MonoTime
.currTime
-stt
;
764 auto meta
= fmtr
.meta
;
767 conwriteln("layouted in ", ett
.total
!"msecs", " milliseconds");
768 auto res
= ReformatWorkComplete(newW
, newH
, cast(shared)book
, cast(shared)lay
, cast(shared)meta
);
770 } catch (Throwable e
) {
771 // here, we are dead and fucked (the exact order doesn't matter)
772 import core
.stdc
.stdlib
: abort
;
773 import core
.stdc
.stdio
: fprintf
, stderr
;
774 import core
.memory
: GC
;
775 import core
.thread
: thread_suspendAll
;
776 GC
.disable(); // yeah
777 thread_suspendAll(); // stop right here, you criminal scum!
778 auto s
= e
.toString();
779 fprintf(stderr
, "\n=== FATAL ===\n%.*s\n", cast(uint)s
.length
, s
.ptr
);
780 abort(); // die, you bitch!
784 send(ownerTid
, QuitWork());