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
;
29 import iv
.nanovg
.oui
.blendish
;
39 // ////////////////////////////////////////////////////////////////////////// //
40 BookText
loadBook (string fname
) {
41 __gshared
bool eliteShipsLoaded
= false;
44 //import core.memory : GC;
45 fname
= fname
.expandTilde
.absolutePath
;
46 //writeln("loading '", fname, "'...");
48 auto stt
= MonoTime
.currTime
;
49 auto book
= new BookText(fname
);
52 { import std
.stdio
; writeln("loaded: '", fname
, "' in ", (MonoTime
.currTime
-stt
).total
!"msecs", " milliseconds"); }
57 foreach (string line
; VFile(buildPath(RcDir
, ".lastfile")).byLineCopy
) {
61 while (lines
.length
&& lines
[$-1].isComment
) {
62 comments
~= lines
[$-1];
63 lines
= lines
[0..$-1];
67 } catch (Exception
) {}
68 try { import std
.file
; mkdirRecurse(RcDir
); } catch (Exception
) {}
69 auto fo
= VFile(buildPath(RcDir
, ".lastfile"), "w");
70 foreach (string s
; lines
) fo
.writeln(s
);
71 foreach_reverse (string s
; comments
) fo
.writeln(s
);
74 stateFileName
= buildPath(RcDir
, fname
.baseName
~".rc");
76 if (!eliteShipsLoaded
) {
78 eliteShipsLoaded
= true;
85 // ////////////////////////////////////////////////////////////////////////// //
86 private struct ImageInfo
{
87 enum MaxIdleFrames
= 5;
93 private __gshared ImageInfo
[] nvgImageIds
;
96 void releaseImages (NVGContext vg
) {
97 if (nvgImageIds
.length
) {
98 foreach (ref ImageInfo nfo
; nvgImageIds
) {
100 if (++nfo
.framesNotUsed
> ImageInfo
.MaxIdleFrames
) {
101 //writeln("freeing image with id ", nfo.iid);
102 vg
.deleteImage(nfo
.iid
);
112 private int registerImage (NVGContext vg
, TrueColorImage img
) {
113 if (vg
is null || img
is null) return -1;
114 uint freeIdx
= uint.max
;
115 foreach (immutable idx
, ref ImageInfo nfo
; nvgImageIds
) {
116 if (nfo
.img
is img
) {
117 assert(nfo
.iid
>= 0);
118 nfo
.framesNotUsed
= 0;
121 if (freeIdx
!= uint.max
&& nfo
.iid
< 0) freeIdx
= cast(uint)idx
;
123 int iid
= vg
.createImageFromMemoryImage(img
); //vg.createImageRGBA(img.width, img.height, img.imageData.bytes[], NVGImageFlags.None);
124 if (iid
< 0) return -1;
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 int iid
= vg
.registerImage(img
);
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 private void fixFont (LayText lay
) {
171 if (lay
.fontStyle
.italic
) {
172 if (lay
.fontStyle
.bold
) lay
.fontStyle
.fontface
= lay
.fontFaceId("textz");
173 else lay
.fontStyle
.fontface
= lay
.fontFaceId("texti");
174 } else if (lay
.fontStyle
.bold
) {
175 if (lay
.fontStyle
.italic
) lay
.fontStyle
.fontface
= lay
.fontFaceId("textz");
176 else lay
.fontStyle
.fontface
= lay
.fontFaceId("textb");
178 lay
.fontStyle
.fontface
= lay
.fontFaceId("text");
182 private void setNormalStyle (LayText lay
) {
183 lay
.fontStyle
.resetAttrs
;
184 lay
.fontStyle
.fontsize
= fsizeText
;
185 lay
.fontStyle
.color
= colorText
.asUint
;
186 lay
.lineStyle
.leftpad
= 0;
187 lay
.lineStyle
.rightpad
= 0;
188 lay
.lineStyle
.paraIndent
= 3;
189 if (optJustify
) lay
.lineStyle
.setJustify
; else lay
.lineStyle
.setLeft
;
193 private void setTitleStyle (LayText lay
) {
194 lay
.fontStyle
.resetAttrs
;
195 lay
.fontStyle
.bold
= true;
196 lay
.fontStyle
.fontsize
= fsizeText
+4;
197 lay
.fontStyle
.color
= colorText
.asUint
;
198 lay
.lineStyle
.leftpad
= 0;
199 lay
.lineStyle
.rightpad
= 0;
200 lay
.lineStyle
.paraIndent
= 0;
201 lay
.lineStyle
.setCenter
;
205 private void setSubtitleStyle (LayText lay
) {
206 lay
.fontStyle
.resetAttrs
;
207 lay
.fontStyle
.bold
= true;
208 lay
.fontStyle
.fontsize
= fsizeText
+2;
209 lay
.fontStyle
.color
= colorText
.asUint
;
210 lay
.lineStyle
.leftpad
= 0;
211 lay
.lineStyle
.rightpad
= 0;
212 lay
.lineStyle
.paraIndent
= 0;
213 lay
.lineStyle
.setCenter
;
217 private void setCiteStyle (LayText lay
) {
218 lay
.fontStyle
.resetAttrs
;
219 lay
.fontStyle
.italic
= true;
220 lay
.fontStyle
.fontsize
= fsizeText
;
221 lay
.fontStyle
.color
= colorText
.asUint
;
222 lay
.lineStyle
.leftpad
= fsizeText
*3;
223 lay
.lineStyle
.rightpad
= fsizeText
*3;
224 lay
.lineStyle
.paraIndent
= 3;
225 if (optJustify
) lay
.lineStyle
.setJustify
; else lay
.lineStyle
.setLeft
;
229 private void setEpigraphStyle (LayText lay
) {
230 lay
.fontStyle
.resetAttrs
;
231 lay
.fontStyle
.italic
= true;
232 lay
.fontStyle
.fontsize
= fsizeText
;
233 lay
.fontStyle
.color
= colorText
.asUint
;
234 lay
.lineStyle
.leftpad
= lay
.width
/3;
235 lay
.lineStyle
.rightpad
= 0;
236 lay
.lineStyle
.paraIndent
= 0;
237 lay
.lineStyle
.setRight
;
241 private void setPoemStyle (LayText lay
) {
242 lay
.fontStyle
.resetAttrs
;
243 lay
.fontStyle
.italic
= true;
244 lay
.fontStyle
.fontsize
= fsizeText
;
245 lay
.fontStyle
.color
= colorText
.asUint
;
246 lay
.lineStyle
.leftpad
= fsizeText
*3;
247 lay
.lineStyle
.rightpad
= 0;
248 lay
.lineStyle
.paraIndent
= 0;
249 lay
.lineStyle
.setLeft
;
254 // ////////////////////////////////////////////////////////////////////////// //
255 public BookMetadata
formatBook (BookText book
, LayText lay
) {
256 assert(book
!is null);
258 BookMetadata meta
= new BookMetadata();
260 void dumpTree (Tag ct
) {
261 while (ct
!is null) {
262 { import std
.stdio
; writeln("tag: ", ct
.name
); }
267 void badTag (Tag tag
, string msg
=null) {
268 import std
.string
: format
, indexOf
;
269 assert(tag
!is null);
271 if (msg
.length
== 0) msg
= "invalid tag: '%s'";
272 if (msg
.indexOf("%s") >= 0) {
273 throw new Exception(msg
.format(tag
.name
));
275 throw new Exception(msg
);
279 void saveTagId (Tag tag
) {
281 import std
.conv
: to
;
282 meta
.ids
~= BookMetadata
.Anc(tag
.id
.to
!dstring
, lay
.nextWordIndex
);
286 void putImage (Tag tag
) {
287 if (tag
is null || tag
.href
.length
< 2 || tag
.href
[0] != '#') return;
288 string iid
= tag
.href
[1..$];
289 //writeln("searching for image with href '", iid, "' (", book.images.length, ")");
290 foreach (immutable idx
, ref BookText
.Image img
; book
.images
) {
292 //writeln("image '", img.id, "' found");
293 lay
.putObject(new BookImage(book
, cast(uint)idx
));
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
;
305 case "strong": ++boldc
; lay
.fontStyle
.bold
= true; break;
306 case "emphasis": ++italicc
; lay
.fontStyle
.italic
= true; break;
307 case "image": putImage(ct
); return;
308 case "style": return;
310 if (ct
.href
.length
) {
311 import std
.conv
: to
;
312 //writeln("href found: '", ct.href, "' (", lay.nextWordIndex, ")");
313 meta
.hrefs
[lay
.nextWordIndex
] = BookMetadata
.Anc(ct
.href
.to
!dstring
, lay
.nextWordIndex
);
315 lay
.fontStyle
.href
= true;
316 lay
.fontStyle
.underline
= true;
317 lay
.fontStyle
.color
= colorTextHref
.asUint
;
320 case "sub": break; // some idiotic files has this
321 case "sup": break; // some idiotic files has this
322 case "": lay
.put(ct
.text
); return;
326 foreach (Tag tag
; ct
.children
) putParaContentInternal(tag
, boldc
, italicc
, underc
);
328 case "strong": if (--boldc
== 0) lay
.fontStyle
.bold
= false; break;
329 case "emphasis": if (--italicc
== 0) lay
.fontStyle
.italic
= false; break;
330 case "a": if (--underc
== 0) lay
.fontStyle
.underline
= false; lay
.fontStyle
.href
= false; break;
336 bool onlyImagePara (Tag ct
) {
337 if (ct
is null) return false;
339 foreach (Tag tag
; ct
.children
) {
340 if (tag
.name
.length
== 0) {
342 if (tag
.text
.xstrip
.length
!= 0) return false;
345 if (count
!= 0 || tag
.name
!= "image") return false;
351 void putTagContents (Tag ct
, int boldc
=0, int italicc
=0, int underc
=0) {
352 if (ct
is null) return;
354 if (onlyImagePara(ct
)) {
356 scope(exit
) lay
.popStyles
;
357 lay
.lineStyle
.setCenter
;
358 foreach (Tag tag
; ct
.children
) putParaContentInternal(tag
, boldc
, italicc
, underc
);
360 foreach (Tag tag
; ct
.children
) putParaContentInternal(tag
, boldc
, italicc
, underc
);
365 void putParas (Tag ct
) {
367 foreach (Tag tag
; ct
.children
) {
368 if (tag
.name
.length
== 0) continue;
369 if (tag
.name
== "p") putTagContents(tag
);
370 else if (tag
.name
== "empty-line") lay
.endLine();
371 else if (tag
.name
== "text-author") {}
372 else if (tag
.name
== "strong") putTagContents(tag
, 1);
377 void putAuthor (Tag ct
) {
379 foreach (Tag tag
; ct
.children
) {
380 if (tag
.name
.length
== 0) continue;
381 if (tag
.name
== "text-author") {
388 void putCite (Tag ct
) {
390 scope(exit
) lay
.popStyles
;
396 void putEpigraph (Tag ct
) {
398 scope(exit
) lay
.popStyles
;
399 setEpigraphStyle(lay
);
404 void putStanza (Tag ct
) {
406 foreach (Tag tag
; ct
.children
) {
407 if (tag
.name
.length
== 0) continue;
408 if (tag
.name
== "text-author") continue;
409 if (tag
.name
== "title") badTag(tag
, "titles in stanzas are not supported yet");
410 if (tag
.name
== "subtitle") badTag(tag
, "subtitles in stanzas are not supported yet");
411 if (tag
.name
== "epigraph") badTag(tag
, "epigraphs in poems are not supported yet");
412 if (tag
.name
== "date") continue;
413 if (tag
.name
== "v") { putTagContents(tag
); continue; }
418 void putPoem (Tag ct
) {
421 scope(exit
) lay
.popStyles
;
423 // put epigraph (not yet)
424 // put title and subtitle
425 foreach (Tag tag
; ct
.children
) {
426 if (tag
.name
== "title") {
428 scope(exit
) lay
.popStyles
;
430 lay
.endPara(); // space
431 } else if (tag
.name
== "subtitle") {
433 scope(exit
) lay
.popStyles
;
435 //lay.endPara(); // space
439 foreach (Tag tag
; ct
.children
) {
440 if (tag
.name
.length
== 0) continue;
441 if (tag
.name
== "text-author") continue;
442 if (tag
.name
== "title") continue;
443 if (tag
.name
== "subtitle") continue;
444 if (tag
.name
== "epigraph") badTag(tag
, "epigraphs in poems are not supported yet");
445 if (tag
.name
== "date") continue;
446 if (tag
.name
== "stanza") { putStanza(tag
); lay
.put(LayText
.EndParaCh
); continue; }
452 void putSection (Tag sc
) {
453 bool sectionRegistered
= false;
455 void registerSection (Tag tag
) {
456 import std
.conv
: to
;
457 if (sectionRegistered
) return;
458 sectionRegistered
= true;
459 string text
= tag
.textContent
.xstrip
;
460 //lay.sections ~= lay.curWordIndex;
462 while (text
.length
) {
463 char ch
= text
.ptr
[0];
465 if (ch
<= ' ' || ch
== 127) {
466 if (name
.length
== 0) continue;
467 if (ch
!= '\n') ch
= ' ';
469 if (name
[$-1] > ' ') name
~= "\n...";
472 if (name
[$-1] > ' ') name
~= ' ';
479 if (name
.length
== 0) name
= "* * *";
480 meta
.sections
~= BookMetadata
.Anc(name
.to
!dstring
, lay
.nextWordIndex
);
484 foreach (Tag tag
; sc
.children
) {
485 if (tag
.name
.length
== 0) continue;
486 if (tag
.name
== "title") {
488 scope(exit
) lay
.popStyles
;
490 registerSection(tag
);
492 } else if (tag
.name
== "subtitle") {
494 scope(exit
) lay
.popStyles
;
495 setSubtitleStyle(lay
);
496 registerSection(tag
);
498 } else if (tag
.name
== "epigraph") {
500 scope(exit
) lay
.popStyles
;
501 setEpigraphStyle(lay
);
503 } else if (tag
.name
== "section") {
505 scope(exit
) lay
.popStyles
;
508 } else if (tag
.name
== "p") {
510 } else if (tag
.name
== "image") {
512 scope(exit
) lay
.popStyles
;
513 lay
.lineStyle
.leftpad
= 0;
514 lay
.lineStyle
.rightpad
= 0;
515 lay
.lineStyle
.paraIndent
= 0;
516 lay
.lineStyle
.setCenter
;
519 } else if (tag
.name
== "empty-line") {
521 } else if (tag
.name
== "cite") {
523 } else if (tag
.name
== "table") {
525 scope(exit
) lay
.popStyles
;
526 lay
.fontStyle
.fontsize
+= 8;
527 lay
.lineStyle
.setCenter
;
528 lay
.put("TABLE SKIPPED");
530 } else if (tag
.name
== "poem") {
532 } else if (tag
.name
== "image") {
533 } else if (tag
.name
== "style") {
541 foreach (Tag tag
; book
.content
.children
) {
542 if (tag
.name
== "title") {
544 scope(exit
) lay
.popStyles
;
549 } else if (tag
.name
== "subtitle") {
551 scope(exit
) lay
.popStyles
;
555 } else if (tag
.name
== "epigraph") {
557 } else if (tag
.name
== "section") {
559 scope(exit
) lay
.popStyles
;
562 } else if (tag
.name
== "image") {
573 // ////////////////////////////////////////////////////////////////////////// //
574 private __gshared LayFontStash laf
; // layouter font stash
577 // ////////////////////////////////////////////////////////////////////////// //
578 private void loadFmtFonts () {
579 laf
= new LayFontStash();
581 laf
.addFont("text", textFontName
);
582 laf
.addFont("texti", textiFontName
);
583 laf
.addFont("textb", textbFontName
);
584 laf
.addFont("textz", textzFontName
);
586 laf
.addFont("mono", monoFontName
);
587 laf
.addFont("monoi", monoiFontName
);
588 laf
.addFont("monob", monobFontName
);
589 laf
.addFont("monoz", monozFontName
);
593 // ////////////////////////////////////////////////////////////////////////// //
595 struct ReformatWork
{
596 shared(BookText
) booktext
;
601 struct ReformatWorkComplete
{
603 shared(BookText
) booktext
;
604 shared(LayText
) laytext
;
605 shared(BookMetadata
) meta
;
612 // ////////////////////////////////////////////////////////////////////////// //
613 void reformatThreadFn (Tid ownerTid
) {
618 int newW
= -1, newH
= -1;
625 //{ import std.stdio; writeln("reformat request received..."); }
626 book
= cast(BookText
)w
.booktext
;
627 newFileName
= w
.bookFileName
;
630 if (newW
< 1) newW
= 1;
631 if (newH
< 1) newH
= 1;
637 if (!doQuit
&& newW
> 0 && newH
> 0) {
640 //writeln("loading new book: '", newFileName, "'");
641 book
= loadBook(newFileName
);
644 int maxWidth
= newW
-4-2-BND_SCROLLBAR_WIDTH
-2;
645 if (maxWidth
< 64) maxWidth
= 64;
648 //writeln("layouting...");
649 auto stt
= MonoTime
.currTime
;
650 auto lay
= new LayText(laf
, maxWidth
);
651 lay
.fontStyle
.color
= colorText
.asUint
;
653 auto meta
= book
.formatBook(lay
);
655 auto ett
= MonoTime
.currTime
-stt
;
656 writeln("layouted in ", ett
.total
!"msecs", " milliseconds");
657 auto res
= ReformatWorkComplete(newW
, newH
, cast(shared)book
, cast(shared)lay
, cast(shared)meta
);
659 } catch (Throwable e
) {
660 // here, we are dead and fucked (the exact order doesn't matter)
661 import core
.stdc
.stdlib
: abort
;
662 import core
.stdc
.stdio
: fprintf
, stderr
;
663 import core
.memory
: GC
;
664 import core
.thread
: thread_suspendAll
;
665 GC
.disable(); // yeah
666 thread_suspendAll(); // stop right here, you criminal scum!
667 auto s
= e
.toString();
668 fprintf(stderr
, "\n=== FATAL ===\n%.*s\n", cast(uint)s
.length
, s
.ptr
);
669 abort(); // die, you bitch!
673 send(ownerTid
, QuitWork());