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"); }
54 currentFileId
= updateFileLastReadTime(fname
, book
.getAuthor(), book
.getTitle(),
55 book
.sequence
, book
.seqnum
);
57 if (!eliteShipsLoaded
) {
59 eliteShipsLoaded
= true;
66 // ////////////////////////////////////////////////////////////////////////// //
67 private struct ImageInfo
{
68 enum MaxIdleFrames
= 5;
74 private __gshared ImageInfo
[] nvgImageIds
;
77 void releaseImages (NVGContext vg
) {
78 if (nvgImageIds
.length
) {
79 foreach (ref ImageInfo nfo
; nvgImageIds
) {
81 if (++nfo
.framesNotUsed
> ImageInfo
.MaxIdleFrames
) {
82 //conwriteln("freeing image with id ", nfo.iid);
83 vg
.deleteImage(nfo
.iid
);
92 private NVGImage
registerImage (NVGContext vg
, TrueColorImage img
) {
93 if (vg
is null || img
is null) return NVGImage
.init
;
94 uint freeIdx
= uint.max
;
95 foreach (immutable idx
, ref ImageInfo nfo
; nvgImageIds
) {
97 assert(nfo
.iid
.valid
);
98 nfo
.framesNotUsed
= 0;
101 if (freeIdx
!= uint.max
&& !nfo
.iid
.valid
) freeIdx
= cast(uint)idx
;
103 NVGImage iid
= vg
.createImageFromMemoryImage(img
); //vg.createImageRGBA(img.width, img.height, img.imageData.bytes[], NVGImageFlags.None);
104 if (!iid
.valid
) return NVGImage
.init
;
105 nvgImageIds
~= ImageInfo(iid
, img
, 0);
110 class BookImage
: LayObject
{
114 this (BookText abook
, uint aidx
) { book
= abook
; imageidx
= aidx
; img
= book
.images
[aidx
].img
; }
115 override int width () { return img
.width
; }
116 override int spacewidth () { return 0; }
117 override int height () { return img
.height
; }
118 override int ascent () { return img
.height
; }
119 override int descent () { return 0; }
120 override bool canbreak () { return true; }
121 override bool spaced () { return false; }
122 override void draw (NVGContext vg
, float x
, float y
) {
125 scope(exit
) vg
.restore();
127 NVGImage iid
= vg
.registerImage(img
);
128 if (!iid
.valid
) return;
129 vg
.fillPaint(vg
.imagePattern(x
, y
, img
.width
, img
.height
, 0, iid
));
130 //vg.globalAlpha(0.5);
131 vg
.rect(x
, y
, img
.width
, img
.height
);
137 // ////////////////////////////////////////////////////////////////////////// //
141 uint wordidx
; // first word
144 Anc
[uint] hrefs
; // `name` is dest
149 // ////////////////////////////////////////////////////////////////////////// //
150 final class FBFormatter
{
162 void formatBook (BookText abook
, int maxWidth
) {
163 assert(abook
!is null);
165 lay
= new LayTextC(laf
, maxWidth
);
166 lay
.fontStyle
.color
= colorText
.asUint
;
168 meta
= new BookMetadata();
171 scope(exit
) book
= null;
180 void dumpTree (Tag ct
) {
181 while (ct
!is null) {
182 conwriteln("tag: ", ct
.name
);
187 void badTag (Tag tag
, string msg
=null) {
188 import std
.string
: format
, indexOf
;
189 assert(tag
!is null);
191 if (msg
.length
== 0) msg
= "invalid tag: '%s'";
192 if (msg
.indexOf("%s") >= 0) {
193 throw new Exception(msg
.format(tag
.name
));
195 throw new Exception(msg
);
199 void saveTagId (Tag tag
) {
201 import std
.conv
: to
;
202 meta
.ids
~= BookMetadata
.Anc(tag
.id
.to
!dstring
, lay
.nextWordIndex
);
206 void registerSection (Tag tag
) {
207 import std
.conv
: to
;
208 string text
= tag
.textContent
.xstrip
;
209 //lay.sections ~= lay.curWordIndex;
211 while (text
.length
) {
212 char ch
= text
.ptr
[0];
214 if (ch
<= ' ' || ch
== 127) {
215 if (name
.length
== 0) continue;
216 if (ch
!= '\n') ch
= ' ';
218 if (name
[$-1] > ' ') name
~= "\n...";
221 if (name
[$-1] > ' ') name
~= ' ';
228 if (name
.length
== 0) name
= "* * *";
229 meta
.sections
~= BookMetadata
.Anc(name
.to
!dstring
, lay
.nextWordIndex
);
232 void putImage (Tag tag
) {
233 if (tag
is null || tag
.href
.length
< 2 || tag
.href
[0] != '#') return;
234 string iid
= tag
.href
[1..$];
235 //conwriteln("searching for image with href '", iid, "' (", book.images.length, ")");
236 foreach (immutable idx
, ref BookText
.Image img
; book
.images
) {
238 //conwriteln("image '", img.id, "' found");
239 lay
.putObject(new BookImage(book
, cast(uint)idx
));
245 void putOneTag(bool allowBad
=false, bool allowText
=true) (Tag tag
) {
246 if (tag
is null) return;
247 if (tag
.name
.length
== 0) {
248 static if (allowText
) {
262 scope(exit
) lay
.popStyles
;
263 lay
.fontStyle
.bold
= true;
268 scope(exit
) lay
.popStyles
;
269 lay
.fontStyle
.italic
= true;
273 case "strikethrough":
275 scope(exit
) lay
.popStyles
;
276 lay
.fontStyle
.strike
= true;
290 scope(exit
) lay
.popStyles
;
292 lay
.lineStyle
.setCenter
;
302 if (tag
.href
.length
) {
303 import std
.conv
: to
;
304 //conwriteln("href found: '", ct.href, "' (", lay.nextWordIndex, ")");
305 meta
.hrefs
[lay
.nextWordIndex
] = BookMetadata
.Anc(tag
.href
.to
!dstring
, lay
.nextWordIndex
);
307 auto c
= lay
.fontStyle
.color
;
308 scope(exit
) { lay
.popStyles
; lay
.fontStyle
.color
= c
; }
309 lay
.fontStyle
.href
= true;
310 lay
.fontStyle
.underline
= true;
311 lay
.fontStyle
.color
= colorTextHref
.asUint
;
319 case "sub": // some idiotic files has this
320 case "sup": // some idiotic files has this
326 scope(exit
) lay
.popStyles
;
334 scope(exit
) lay
.popStyles
;
344 case "v": // for stanzas
349 case "date": // for poem
352 scope(exit
) lay
.popStyles
;
353 lay
.fontStyle
.bold
= true;
354 lay
.fontStyle
.italic
= true;
355 lay
.lineStyle
.setRight
;
368 scope(exit
) lay
.popStyles
;
369 lay
.fontStyle
.bold
= true;
370 lay
.lineStyle
.setCenter
;
381 scope(exit
) lay
.popStyles
;
383 registerSection(tag
);
385 if (tag
.text
.length
== 0) lay
.endPara();
390 scope(exit
) lay
.popStyles
;
393 if (tag
.text
.length
== 0) lay
.endPara();
398 scope(exit
) lay
.popStyles
;
399 //auto olpad = lay.lineStyle.leftpad;
400 auto orpad
= lay
.lineStyle
.rightpad
;
402 //lay.lineStyle.leftpad = lay.lineStyle.leftpad+olpad;
403 lay
.lineStyle
.rightpad
= lay
.lineStyle
.rightpad
+orpad
;
414 scope(exit
) lay
.popStyles
;
415 lay
.fontStyle
.fontsize
+= 8;
416 lay
.lineStyle
.setCenter
;
418 lay
.put("TABLE SKIPPED");
423 static if (allowBad
) {
431 bool onlyImagePara (Tag ct
) {
432 if (ct
is null) return false;
434 foreach (Tag tag
; ct
.children
) {
435 if (tag
.name
.length
== 0) {
437 if (tag
.text
.xstrip
.length
!= 0) return false;
440 if (count
!= 0 || tag
.name
!= "image") return false;
446 void putTagContents
/*(bool xdump=false)*/ (Tag ct
) {
447 if (ct
is null) return;
449 if (onlyImagePara(ct
)) {
451 scope(exit
) lay
.popStyles
;
453 lay
.lineStyle
.setCenter
;
454 foreach (Tag tag
; ct
.children
) putOneTag(tag
);
457 //if (ct.name == "subtitle") conwriteln("text: [", ct.children[0].text, "]");
458 foreach (Tag tag
; ct
.children
) {
459 //static if (xdump) conwriteln("text: [", tag.text, "]");
466 void putParas (Tag ct
) {
467 if (ct
is null) return;
472 void putAuthor (Tag ct
) {
474 foreach (Tag tag
; ct
.children
) {
475 if (tag
.name
.length
== 0) continue;
476 if (tag
.name
== "text-author") {
483 void putCite (Tag ct
) {
485 scope(exit
) lay
.popStyles
;
491 void putEpigraph (Tag ct
) {
496 void putPoem (Tag ct
) {
499 scope(exit
) lay
.popStyles
;
501 // put epigraph (not yet)
502 // put title and subtitle
503 foreach (Tag tag
; ct
.children
) {
504 if (tag
.name
== "title") {
506 scope(exit
) lay
.popStyles
;
507 if (!hasSections
) registerSection(tag
);
509 lay
.endPara(); // space
510 } else if (tag
.name
== "subtitle") {
512 scope(exit
) lay
.popStyles
;
514 //lay.endPara(); // space
518 foreach (Tag tag
; ct
.children
) {
519 putOneTag
!(false, false)(tag
); // skip text without tags
526 foreach (Tag tag
; book
.content
.children
) {
532 static public void fixFont (LayFontStash laf
, ref LayFontStyle st
) nothrow @safe @nogc {
535 if (st
.bold
) st
.fontface
= laf
.fontFaceId("monoz");
536 else st
.fontface
= laf
.fontFaceId("monoi");
537 } else if (st
.bold
) {
538 if (st
.italic
) st
.fontface
= laf
.fontFaceId("monoz");
539 else st
.fontface
= laf
.fontFaceId("monob");
541 st
.fontface
= laf
.fontFaceId("mono");
545 if (st
.bold
) st
.fontface
= laf
.fontFaceId("textz");
546 else st
.fontface
= laf
.fontFaceId("texti");
547 } else if (st
.bold
) {
548 if (st
.italic
) st
.fontface
= laf
.fontFaceId("textz");
549 else st
.fontface
= laf
.fontFaceId("textb");
551 st
.fontface
= laf
.fontFaceId("text");
556 void setNormalStyle () {
557 lay
.fontStyle
.resetAttrs
;
558 lay
.fontStyle
.fontsize
= fsizeText
;
559 lay
.fontStyle
.color
= colorText
.asUint
;
560 lay
.lineStyle
.leftpad
= 0;
561 lay
.lineStyle
.rightpad
= 0;
562 lay
.lineStyle
.paraIndent
= 3;
563 if (optJustify
) lay
.lineStyle
.setJustify
; else lay
.lineStyle
.setLeft
;
567 void setTitleStyle () {
568 lay
.fontStyle
.resetAttrs
;
569 lay
.fontStyle
.bold
= true;
570 lay
.fontStyle
.fontsize
= fsizeText
+4;
571 lay
.fontStyle
.color
= colorText
.asUint
;
572 lay
.lineStyle
.leftpad
= 0;
573 lay
.lineStyle
.rightpad
= 0;
574 lay
.lineStyle
.paraIndent
= 0;
575 lay
.lineStyle
.setCenter
;
579 void setSubtitleStyle () {
580 lay
.fontStyle
.resetAttrs
;
581 lay
.fontStyle
.bold
= true;
582 lay
.fontStyle
.fontsize
= fsizeText
+2;
583 lay
.fontStyle
.color
= colorText
.asUint
;
584 lay
.lineStyle
.leftpad
= 0;
585 lay
.lineStyle
.rightpad
= 0;
586 lay
.lineStyle
.paraIndent
= 0;
587 lay
.lineStyle
.setCenter
;
591 void setCiteStyle () {
592 lay
.fontStyle
.resetAttrs
;
593 lay
.fontStyle
.italic
= true;
594 lay
.fontStyle
.fontsize
= fsizeText
;
595 lay
.fontStyle
.color
= colorText
.asUint
;
596 lay
.lineStyle
.leftpad
= fsizeText
*3;
597 lay
.lineStyle
.rightpad
= fsizeText
*3;
598 lay
.lineStyle
.paraIndent
= 3;
599 if (optJustify
) lay
.lineStyle
.setJustify
; else lay
.lineStyle
.setLeft
;
603 void setEpigraphStyle () {
604 lay
.fontStyle
.resetAttrs
;
605 lay
.fontStyle
.italic
= true;
606 lay
.fontStyle
.fontsize
= fsizeText
;
607 lay
.fontStyle
.color
= colorText
.asUint
;
608 lay
.lineStyle
.leftpad
= lay
.width
/3;
609 lay
.lineStyle
.rightpad
= 0;
610 lay
.lineStyle
.paraIndent
= 0;
611 lay
.lineStyle
.setRight
;
615 void setPoemStyle () {
616 lay
.fontStyle
.resetAttrs
;
617 lay
.fontStyle
.italic
= true;
618 lay
.fontStyle
.fontsize
= fsizeText
;
619 lay
.fontStyle
.color
= colorText
.asUint
;
620 lay
.lineStyle
.leftpad
= fsizeText
*3;
621 lay
.lineStyle
.rightpad
= 0;
622 lay
.lineStyle
.paraIndent
= 0;
623 lay
.lineStyle
.setLeft
;
627 void setCodeStyle () {
628 lay
.fontStyle
.resetAttrs
;
629 lay
.fontStyle
.monospace
= true;
630 lay
.fontStyle
.italic
= false;
631 lay
.fontStyle
.fontsize
= fsizeText
;
632 lay
.fontStyle
.color
= colorText
.asUint
;
633 lay
.lineStyle
.leftpad
= fsizeText
*3;
634 lay
.lineStyle
.rightpad
= fsizeText
*3;
635 lay
.lineStyle
.paraIndent
= 0;
636 lay
.lineStyle
.setLeft
;
637 //lay.fontStyle.fontface = lay.fontFaceId("mono");
642 // ////////////////////////////////////////////////////////////////////////// //
643 private __gshared LayFontStash laf
; // layouter font stash
646 // ////////////////////////////////////////////////////////////////////////// //
647 private void loadFmtFonts (/*NVGContext nvg*/) {
648 import std
.functional
: toDelegate
;
650 //if (nvg !is null) { import core.stdc.stdio : printf; printf("reusing NVG context...\n"); }
651 laf
= new LayFontStash(/*nvg*/);
652 laf
.fixFontDG
= toDelegate(&FBFormatter
.fixFont
);
654 laf
.addFont("text", textFontName
);
655 laf
.addFont("texti", textiFontName
);
656 laf
.addFont("textb", textbFontName
);
657 laf
.addFont("textz", textzFontName
);
659 laf
.addFont("mono", monoFontName
);
660 laf
.addFont("monoi", monoiFontName
);
661 laf
.addFont("monob", monobFontName
);
662 laf
.addFont("monoz", monozFontName
);
667 // ////////////////////////////////////////////////////////////////////////// //
669 struct ReformatWork
{
670 shared(BookText
) booktext
;
671 //shared(NVGContext) nvg; // can be null
676 struct ReformatWorkComplete
{
678 shared(BookText
) booktext
;
679 shared(LayTextC
) laytext
;
680 shared(BookMetadata
) meta
;
687 // ////////////////////////////////////////////////////////////////////////// //
688 void reformatThreadFn (Tid ownerTid
) {
692 int newW
= -1, newH
= -1;
693 //NVGContext lastnvg = null;
700 //{ conwriteln("reformat request received..."); }
701 book
= cast(BookText
)w
.booktext
;
702 newFileName
= w
.bookFileName
;
705 if (newW
< 1) newW
= 1;
706 if (newH
< 1) newH
= 1;
707 //lastnvg = cast(NVGContext)w.nvg;
713 if (!doQuit
&& newW
> 0 && newH
> 0) {
716 //conwriteln("loading new book: '", newFileName, "'");
717 book
= loadBook(newFileName
);
720 int maxWidth
= newW
-4-2-BND_SCROLLBAR_WIDTH
-2;
721 if (maxWidth
< 64) maxWidth
= 64;
726 //conwriteln("layouting...");
727 auto fmtr
= new FBFormatter();
729 auto stt
= MonoTime
.currTime
;
731 auto lay = new LayText(laf, maxWidth);
732 lay.fontStyle.color = colorText.asUint;
733 auto meta = book.formatBook(lay);
735 fmtr
.formatBook(book
, maxWidth
);
736 auto ett
= MonoTime
.currTime
-stt
;
738 auto meta
= fmtr
.meta
;
741 conwriteln("layouted in ", ett
.total
!"msecs", " milliseconds");
742 auto res
= ReformatWorkComplete(newW
, newH
, cast(shared)book
, cast(shared)lay
, cast(shared)meta
);
744 } catch (Throwable e
) {
745 // here, we are dead and fucked (the exact order doesn't matter)
746 import core
.stdc
.stdlib
: abort
;
747 import core
.stdc
.stdio
: fprintf
, stderr
;
748 import core
.memory
: GC
;
749 import core
.thread
: thread_suspendAll
;
750 GC
.disable(); // yeah
751 thread_suspendAll(); // stop right here, you criminal scum!
752 auto s
= e
.toString();
753 fprintf(stderr
, "\n=== FATAL ===\n%.*s\n", cast(uint)s
.length
, s
.ptr
);
754 abort(); // die, you bitch!
758 send(ownerTid
, QuitWork());