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
.nanovega
.blendish
;
31 import iv
.nanovega
.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
);
112 private NVGImage
registerImage (NVGContext vg
, TrueColorImage img
) {
113 if (vg
is null || img
is null) return NVGImage
.init
;
114 uint freeIdx
= uint.max
;
115 foreach (immutable idx
, ref ImageInfo nfo
; nvgImageIds
) {
116 if (nfo
.img
is img
) {
117 assert(nfo
.iid
.valid
);
118 nfo
.framesNotUsed
= 0;
121 if (freeIdx
!= uint.max
&& !nfo
.iid
.valid
) freeIdx
= cast(uint)idx
;
123 NVGImage iid
= vg
.createImageFromMemoryImage(img
); //vg.createImageRGBA(img.width, img.height, img.imageData.bytes[], NVGImageFlags.None);
124 if (!iid
.valid
) return NVGImage
.init
;
125 nvgImageIds
~= ImageInfo(iid
, img
, 0);
130 class BookImage
: LayObject
{
134 this (BookText abook
, uint aidx
) { book
= abook
; imageidx
= aidx
; img
= book
.images
[aidx
].img
; }
135 override int width () { return img
.width
; }
136 override int spacewidth () { return 0; }
137 override int height () { return img
.height
; }
138 override int ascent () { return img
.height
; }
139 override int descent () { return 0; }
140 override bool canbreak () { return true; }
141 override bool spaced () { return false; }
142 override void draw (NVGContext vg
, float x
, float y
) {
145 scope(exit
) vg
.restore();
147 NVGImage iid
= vg
.registerImage(img
);
148 if (!iid
.valid
) return;
149 vg
.fillPaint(vg
.imagePattern(x
, y
, img
.width
, img
.height
, 0, iid
));
150 //vg.globalAlpha(0.5);
151 vg
.rect(x
, y
, img
.width
, img
.height
);
157 // ////////////////////////////////////////////////////////////////////////// //
161 uint wordidx
; // first word
164 Anc
[uint] hrefs
; // `name` is dest
169 // ////////////////////////////////////////////////////////////////////////// //
170 final class FBFormatter
{
182 void formatBook (BookText abook
, int maxWidth
) {
183 assert(abook
!is null);
185 lay
= new LayTextC(laf
, maxWidth
);
186 lay
.fontStyle
.color
= colorText
.asUint
;
188 meta
= new BookMetadata();
191 scope(exit
) book
= null;
200 void dumpTree (Tag ct
) {
201 while (ct
!is null) {
202 conwriteln("tag: ", ct
.name
);
207 void badTag (Tag tag
, string msg
=null) {
208 import std
.string
: format
, indexOf
;
209 assert(tag
!is null);
211 if (msg
.length
== 0) msg
= "invalid tag: '%s'";
212 if (msg
.indexOf("%s") >= 0) {
213 throw new Exception(msg
.format(tag
.name
));
215 throw new Exception(msg
);
219 void saveTagId (Tag tag
) {
221 import std
.conv
: to
;
222 meta
.ids
~= BookMetadata
.Anc(tag
.id
.to
!dstring
, lay
.nextWordIndex
);
226 void registerSection (Tag tag
) {
227 import std
.conv
: to
;
228 string text
= tag
.textContent
.xstrip
;
229 //lay.sections ~= lay.curWordIndex;
231 while (text
.length
) {
232 char ch
= text
.ptr
[0];
234 if (ch
<= ' ' || ch
== 127) {
235 if (name
.length
== 0) continue;
236 if (ch
!= '\n') ch
= ' ';
238 if (name
[$-1] > ' ') name
~= "\n...";
241 if (name
[$-1] > ' ') name
~= ' ';
248 if (name
.length
== 0) name
= "* * *";
249 meta
.sections
~= BookMetadata
.Anc(name
.to
!dstring
, lay
.nextWordIndex
);
252 void putImage (Tag tag
) {
253 if (tag
is null || tag
.href
.length
< 2 || tag
.href
[0] != '#') return;
254 string iid
= tag
.href
[1..$];
255 //conwriteln("searching for image with href '", iid, "' (", book.images.length, ")");
256 foreach (immutable idx
, ref BookText
.Image img
; book
.images
) {
258 //conwriteln("image '", img.id, "' found");
259 lay
.putObject(new BookImage(book
, cast(uint)idx
));
265 void putOneTag(bool allowBad
=false, bool allowText
=true) (Tag tag
) {
266 if (tag
is null) return;
267 if (tag
.name
.length
== 0) {
268 static if (allowText
) {
282 scope(exit
) lay
.popStyles
;
283 lay
.fontStyle
.bold
= true;
288 scope(exit
) lay
.popStyles
;
289 lay
.fontStyle
.italic
= true;
293 case "strikethrough":
295 scope(exit
) lay
.popStyles
;
296 lay
.fontStyle
.strike
= true;
310 scope(exit
) lay
.popStyles
;
312 lay
.lineStyle
.setCenter
;
322 if (tag
.href
.length
) {
323 import std
.conv
: to
;
324 //conwriteln("href found: '", ct.href, "' (", lay.nextWordIndex, ")");
325 meta
.hrefs
[lay
.nextWordIndex
] = BookMetadata
.Anc(tag
.href
.to
!dstring
, lay
.nextWordIndex
);
327 auto c
= lay
.fontStyle
.color
;
328 scope(exit
) { lay
.popStyles
; lay
.fontStyle
.color
= c
; }
329 lay
.fontStyle
.href
= true;
330 lay
.fontStyle
.underline
= true;
331 lay
.fontStyle
.color
= colorTextHref
.asUint
;
339 case "sub": // some idiotic files has this
340 case "sup": // some idiotic files has this
346 scope(exit
) lay
.popStyles
;
354 scope(exit
) lay
.popStyles
;
364 case "v": // for stanzas
369 case "date": // for poem
372 scope(exit
) lay
.popStyles
;
373 lay
.fontStyle
.bold
= true;
374 lay
.fontStyle
.italic
= true;
375 lay
.lineStyle
.setRight
;
388 scope(exit
) lay
.popStyles
;
389 lay
.fontStyle
.bold
= true;
390 lay
.lineStyle
.setCenter
;
401 scope(exit
) lay
.popStyles
;
403 registerSection(tag
);
405 if (tag
.text
.length
== 0) lay
.endPara();
410 scope(exit
) lay
.popStyles
;
413 if (tag
.text
.length
== 0) lay
.endPara();
418 scope(exit
) lay
.popStyles
;
419 //auto olpad = lay.lineStyle.leftpad;
420 auto orpad
= lay
.lineStyle
.rightpad
;
422 //lay.lineStyle.leftpad = lay.lineStyle.leftpad+olpad;
423 lay
.lineStyle
.rightpad
= lay
.lineStyle
.rightpad
+orpad
;
434 scope(exit
) lay
.popStyles
;
435 lay
.fontStyle
.fontsize
+= 8;
436 lay
.lineStyle
.setCenter
;
438 lay
.put("TABLE SKIPPED");
443 static if (allowBad
) {
451 bool onlyImagePara (Tag ct
) {
452 if (ct
is null) return false;
454 foreach (Tag tag
; ct
.children
) {
455 if (tag
.name
.length
== 0) {
457 if (tag
.text
.xstrip
.length
!= 0) return false;
460 if (count
!= 0 || tag
.name
!= "image") return false;
466 void putTagContents
/*(bool xdump=false)*/ (Tag ct
) {
467 if (ct
is null) return;
469 if (onlyImagePara(ct
)) {
471 scope(exit
) lay
.popStyles
;
473 lay
.lineStyle
.setCenter
;
474 foreach (Tag tag
; ct
.children
) putOneTag(tag
);
477 //if (ct.name == "subtitle") conwriteln("text: [", ct.children[0].text, "]");
478 foreach (Tag tag
; ct
.children
) {
479 //static if (xdump) conwriteln("text: [", tag.text, "]");
486 void putParas (Tag ct
) {
487 if (ct
is null) return;
492 void putAuthor (Tag ct
) {
494 foreach (Tag tag
; ct
.children
) {
495 if (tag
.name
.length
== 0) continue;
496 if (tag
.name
== "text-author") {
503 void putCite (Tag ct
) {
505 scope(exit
) lay
.popStyles
;
511 void putEpigraph (Tag ct
) {
516 void putPoem (Tag ct
) {
519 scope(exit
) lay
.popStyles
;
521 // put epigraph (not yet)
522 // put title and subtitle
523 foreach (Tag tag
; ct
.children
) {
524 if (tag
.name
== "title") {
526 scope(exit
) lay
.popStyles
;
527 if (!hasSections
) registerSection(tag
);
529 lay
.endPara(); // space
530 } else if (tag
.name
== "subtitle") {
532 scope(exit
) lay
.popStyles
;
534 //lay.endPara(); // space
538 foreach (Tag tag
; ct
.children
) {
539 putOneTag
!(false, false)(tag
); // skip text without tags
546 foreach (Tag tag
; book
.content
.children
) {
552 static public void fixFont (LayFontStash laf
, ref LayFontStyle st
) nothrow @safe @nogc {
555 if (st
.bold
) st
.fontface
= laf
.fontFaceId("monoz");
556 else st
.fontface
= laf
.fontFaceId("monoi");
557 } else if (st
.bold
) {
558 if (st
.italic
) st
.fontface
= laf
.fontFaceId("monoz");
559 else st
.fontface
= laf
.fontFaceId("monob");
561 st
.fontface
= laf
.fontFaceId("mono");
565 if (st
.bold
) st
.fontface
= laf
.fontFaceId("textz");
566 else st
.fontface
= laf
.fontFaceId("texti");
567 } else if (st
.bold
) {
568 if (st
.italic
) st
.fontface
= laf
.fontFaceId("textz");
569 else st
.fontface
= laf
.fontFaceId("textb");
571 st
.fontface
= laf
.fontFaceId("text");
576 void setNormalStyle () {
577 lay
.fontStyle
.resetAttrs
;
578 lay
.fontStyle
.fontsize
= fsizeText
;
579 lay
.fontStyle
.color
= colorText
.asUint
;
580 lay
.lineStyle
.leftpad
= 0;
581 lay
.lineStyle
.rightpad
= 0;
582 lay
.lineStyle
.paraIndent
= 3;
583 if (optJustify
) lay
.lineStyle
.setJustify
; else lay
.lineStyle
.setLeft
;
587 void setTitleStyle () {
588 lay
.fontStyle
.resetAttrs
;
589 lay
.fontStyle
.bold
= true;
590 lay
.fontStyle
.fontsize
= fsizeText
+4;
591 lay
.fontStyle
.color
= colorText
.asUint
;
592 lay
.lineStyle
.leftpad
= 0;
593 lay
.lineStyle
.rightpad
= 0;
594 lay
.lineStyle
.paraIndent
= 0;
595 lay
.lineStyle
.setCenter
;
599 void setSubtitleStyle () {
600 lay
.fontStyle
.resetAttrs
;
601 lay
.fontStyle
.bold
= true;
602 lay
.fontStyle
.fontsize
= fsizeText
+2;
603 lay
.fontStyle
.color
= colorText
.asUint
;
604 lay
.lineStyle
.leftpad
= 0;
605 lay
.lineStyle
.rightpad
= 0;
606 lay
.lineStyle
.paraIndent
= 0;
607 lay
.lineStyle
.setCenter
;
611 void setCiteStyle () {
612 lay
.fontStyle
.resetAttrs
;
613 lay
.fontStyle
.italic
= true;
614 lay
.fontStyle
.fontsize
= fsizeText
;
615 lay
.fontStyle
.color
= colorText
.asUint
;
616 lay
.lineStyle
.leftpad
= fsizeText
*3;
617 lay
.lineStyle
.rightpad
= fsizeText
*3;
618 lay
.lineStyle
.paraIndent
= 3;
619 if (optJustify
) lay
.lineStyle
.setJustify
; else lay
.lineStyle
.setLeft
;
623 void setEpigraphStyle () {
624 lay
.fontStyle
.resetAttrs
;
625 lay
.fontStyle
.italic
= true;
626 lay
.fontStyle
.fontsize
= fsizeText
;
627 lay
.fontStyle
.color
= colorText
.asUint
;
628 lay
.lineStyle
.leftpad
= lay
.width
/3;
629 lay
.lineStyle
.rightpad
= 0;
630 lay
.lineStyle
.paraIndent
= 0;
631 lay
.lineStyle
.setRight
;
635 void setPoemStyle () {
636 lay
.fontStyle
.resetAttrs
;
637 lay
.fontStyle
.italic
= true;
638 lay
.fontStyle
.fontsize
= fsizeText
;
639 lay
.fontStyle
.color
= colorText
.asUint
;
640 lay
.lineStyle
.leftpad
= fsizeText
*3;
641 lay
.lineStyle
.rightpad
= 0;
642 lay
.lineStyle
.paraIndent
= 0;
643 lay
.lineStyle
.setLeft
;
647 void setCodeStyle () {
648 lay
.fontStyle
.resetAttrs
;
649 lay
.fontStyle
.monospace
= true;
650 lay
.fontStyle
.italic
= false;
651 lay
.fontStyle
.fontsize
= fsizeText
;
652 lay
.fontStyle
.color
= colorText
.asUint
;
653 lay
.lineStyle
.leftpad
= fsizeText
*3;
654 lay
.lineStyle
.rightpad
= fsizeText
*3;
655 lay
.lineStyle
.paraIndent
= 0;
656 lay
.lineStyle
.setLeft
;
657 //lay.fontStyle.fontface = lay.fontFaceId("mono");
662 // ////////////////////////////////////////////////////////////////////////// //
663 private __gshared LayFontStash laf
; // layouter font stash
666 // ////////////////////////////////////////////////////////////////////////// //
667 private void loadFmtFonts (/*NVGContext nvg*/) {
668 import std
.functional
: toDelegate
;
670 //if (nvg !is null) { import core.stdc.stdio : printf; printf("reusing NVG context...\n"); }
671 laf
= new LayFontStash(/*nvg*/);
672 laf
.fixFontDG
= toDelegate(&FBFormatter
.fixFont
);
674 laf
.addFont("text", textFontName
);
675 laf
.addFont("texti", textiFontName
);
676 laf
.addFont("textb", textbFontName
);
677 laf
.addFont("textz", textzFontName
);
679 laf
.addFont("mono", monoFontName
);
680 laf
.addFont("monoi", monoiFontName
);
681 laf
.addFont("monob", monobFontName
);
682 laf
.addFont("monoz", monozFontName
);
687 // ////////////////////////////////////////////////////////////////////////// //
689 struct ReformatWork
{
690 shared(BookText
) booktext
;
691 //shared(NVGContext) nvg; // can be null
696 struct ReformatWorkComplete
{
698 shared(BookText
) booktext
;
699 shared(LayTextC
) laytext
;
700 shared(BookMetadata
) meta
;
707 // ////////////////////////////////////////////////////////////////////////// //
708 void reformatThreadFn (Tid ownerTid
) {
712 int newW
= -1, newH
= -1;
713 //NVGContext lastnvg = null;
720 //{ conwriteln("reformat request received..."); }
721 book
= cast(BookText
)w
.booktext
;
722 newFileName
= w
.bookFileName
;
725 if (newW
< 1) newW
= 1;
726 if (newH
< 1) newH
= 1;
727 //lastnvg = cast(NVGContext)w.nvg;
733 if (!doQuit
&& newW
> 0 && newH
> 0) {
736 //conwriteln("loading new book: '", newFileName, "'");
737 book
= loadBook(newFileName
);
740 int maxWidth
= newW
-4-2-BND_SCROLLBAR_WIDTH
-2;
741 if (maxWidth
< 64) maxWidth
= 64;
746 //conwriteln("layouting...");
747 auto fmtr
= new FBFormatter();
749 auto stt
= MonoTime
.currTime
;
751 auto lay = new LayText(laf, maxWidth);
752 lay.fontStyle.color = colorText.asUint;
753 auto meta = book.formatBook(lay);
755 fmtr
.formatBook(book
, maxWidth
);
756 auto ett
= MonoTime
.currTime
-stt
;
758 auto meta
= fmtr
.meta
;
761 conwriteln("layouted in ", ett
.total
!"msecs", " milliseconds");
762 auto res
= ReformatWorkComplete(newW
, newH
, cast(shared)book
, cast(shared)lay
, cast(shared)meta
);
764 } catch (Throwable e
) {
765 // here, we are dead and fucked (the exact order doesn't matter)
766 import core
.stdc
.stdlib
: abort
;
767 import core
.stdc
.stdio
: fprintf
, stderr
;
768 import core
.memory
: GC
;
769 import core
.thread
: thread_suspendAll
;
770 GC
.disable(); // yeah
771 thread_suspendAll(); // stop right here, you criminal scum!
772 auto s
= e
.toString();
773 fprintf(stderr
, "\n=== FATAL ===\n%.*s\n", cast(uint)s
.length
, s
.ptr
);
774 abort(); // die, you bitch!
778 send(ownerTid
, QuitWork());