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
;
31 import iv
.nanovg
.oui
.blendish
;
41 // ////////////////////////////////////////////////////////////////////////// //
42 BookText
loadBook (string fname
) {
43 __gshared
bool eliteShipsLoaded
= false;
46 //import core.memory : GC;
47 fname
= fname
.expandTilde
.absolutePath
;
48 //writeln("loading '", fname, "'...");
50 auto stt
= MonoTime
.currTime
;
51 auto book
= new BookText(fname
);
54 { import std
.stdio
; writeln("loaded: '", fname
, "' in ", (MonoTime
.currTime
-stt
).total
!"msecs", " milliseconds"); }
59 foreach (string line
; VFile(buildPath(RcDir
, ".lastfile")).byLineCopy
) {
63 while (lines
.length
&& lines
[$-1].isComment
) {
64 comments
~= lines
[$-1];
65 lines
= lines
[0..$-1];
69 } catch (Exception
) {}
70 try { import std
.file
; mkdirRecurse(RcDir
); } catch (Exception
) {}
71 auto fo
= VFile(buildPath(RcDir
, ".lastfile"), "w");
72 foreach (string s
; lines
) fo
.writeln(s
);
73 foreach_reverse (string s
; comments
) fo
.writeln(s
);
76 stateFileName
= buildPath(RcDir
, fname
.baseName
~".rc");
78 if (!eliteShipsLoaded
) {
80 eliteShipsLoaded
= true;
87 // ////////////////////////////////////////////////////////////////////////// //
88 private __gshared
int[] nvgImageIds
;
90 void releaseImages (NVGContext vg
) {
91 if (nvgImageIds
.length
) {
92 foreach (int iid
; nvgImageIds
) vg
.deleteImage(iid
);
93 nvgImageIds
.length
= 0;
94 nvgImageIds
.assumeSafeAppend
;
99 class BookImage
: LayObject
{
103 this (BookText abook
, uint aidx
) { book
= abook
; imageidx
= aidx
; img
= book
.images
[aidx
].img
; }
104 override int width () { return img
.width
; }
105 override int spacewidth () { return 0; }
106 override int height () { return img
.height
; }
107 override int ascent () { return img
.height
; }
108 override int descent () { return 0; }
109 override bool canbreak () { return true; }
110 override bool spaced () { return false; }
111 override void draw (NVGContext vg
, float x
, float y
) {
114 scope(exit
) vg
.restore();
116 int iid
= vg
.createImageFromMemoryImage(img
); //vg.createImageRGBA(img.width, img.height, img.imageData.bytes[], NVGImageFlags.None);
117 if (iid
< 0) { writeln("WTF?!"); iid
= -666; return; }
119 vg
.fillPaint(vg
.imagePattern(x
, y
, img
.width
, img
.height
, 0, iid
));
120 //vg.globalAlpha(0.5);
121 vg
.rect(x
, y
, img
.width
, img
.height
);
127 // ////////////////////////////////////////////////////////////////////////// //
131 uint wordidx
; // first word
134 Anc
[] hrefs
; // `name` is dest
139 // ////////////////////////////////////////////////////////////////////////// //
140 public BookMetadata
formatBook (BookText book
, LayText lay
) {
141 assert(book
!is null);
143 BookMetadata meta
= new BookMetadata();
145 static void fixFont (LayText lay
) {
146 if (lay
.fontStyle
.italic
) {
147 if (lay
.fontStyle
.bold
) lay
.fontStyle
.fontface
= lay
.fontFaceId("textz");
148 else lay
.fontStyle
.fontface
= lay
.fontFaceId("texti");
149 } else if (lay
.fontStyle
.bold
) {
150 if (lay
.fontStyle
.italic
) lay
.fontStyle
.fontface
= lay
.fontFaceId("textz");
151 else lay
.fontStyle
.fontface
= lay
.fontFaceId("textb");
153 lay
.fontStyle
.fontface
= lay
.fontFaceId("text");
157 static void setNormalStyle (LayText lay
) {
158 lay
.fontStyle
.resetAttrs
;
159 lay
.fontStyle
.fontsize
= fsizeText
;
160 lay
.fontStyle
.color
= colorText
.asUint
;
161 lay
.lineStyle
.leftpad
= 0;
162 lay
.lineStyle
.rightpad
= 0;
163 lay
.lineStyle
.paraIndent
= 3;
164 if (optJustify
) lay
.lineStyle
.setJustify
; else lay
.lineStyle
.setLeft
;
168 static void setTitleStyle (LayText lay
) {
169 lay
.fontStyle
.resetAttrs
;
170 lay
.fontStyle
.bold
= true;
171 lay
.fontStyle
.fontsize
= fsizeText
+4;
172 lay
.fontStyle
.color
= colorText
.asUint
;
173 lay
.lineStyle
.leftpad
= 0;
174 lay
.lineStyle
.rightpad
= 0;
175 lay
.lineStyle
.paraIndent
= 0;
176 lay
.lineStyle
.setCenter
;
180 static void setSubtitleStyle (LayText lay
) {
181 lay
.fontStyle
.resetAttrs
;
182 lay
.fontStyle
.bold
= true;
183 lay
.fontStyle
.fontsize
= fsizeText
+2;
184 lay
.fontStyle
.color
= colorText
.asUint
;
185 lay
.lineStyle
.leftpad
= 0;
186 lay
.lineStyle
.rightpad
= 0;
187 lay
.lineStyle
.paraIndent
= 0;
188 lay
.lineStyle
.setCenter
;
192 static void setCiteStyle (LayText lay
) {
193 lay
.fontStyle
.resetAttrs
;
194 lay
.fontStyle
.italic
= true;
195 lay
.fontStyle
.fontsize
= fsizeText
;
196 lay
.fontStyle
.color
= colorText
.asUint
;
197 lay
.lineStyle
.leftpad
= fsizeText
*3;
198 lay
.lineStyle
.rightpad
= fsizeText
*3;
199 lay
.lineStyle
.paraIndent
= 3;
200 if (optJustify
) lay
.lineStyle
.setJustify
; else lay
.lineStyle
.setLeft
;
204 static void setEpigraphStyle (LayText lay
) {
205 lay
.fontStyle
.resetAttrs
;
206 lay
.fontStyle
.italic
= true;
207 lay
.fontStyle
.fontsize
= fsizeText
;
208 lay
.fontStyle
.color
= colorText
.asUint
;
209 lay
.lineStyle
.leftpad
= lay
.width
/2;
210 lay
.lineStyle
.rightpad
= 0;
211 lay
.lineStyle
.paraIndent
= 0;
212 lay
.lineStyle
.setRight
;
216 static void setPoemStyle (LayText lay
) {
217 lay
.fontStyle
.resetAttrs
;
218 lay
.fontStyle
.italic
= true;
219 lay
.fontStyle
.fontsize
= fsizeText
;
220 lay
.fontStyle
.color
= colorText
.asUint
;
221 lay
.lineStyle
.leftpad
= fsizeText
*3;
222 lay
.lineStyle
.rightpad
= 0;
223 lay
.lineStyle
.paraIndent
= 0;
224 lay
.lineStyle
.setLeft
;
228 void dumpTree (Tag ct
) {
229 while (ct
!is null) {
230 { import std
.stdio
; writeln("tag: ", ct
.name
); }
235 void badTag (Tag tag
, string msg
=null) {
236 import std
.string
: format
, indexOf
;
237 assert(tag
!is null);
239 if (msg
.length
== 0) msg
= "invalid tag: '%s'";
240 if (msg
.indexOf("%s") >= 0) {
241 throw new Exception(msg
.format(tag
.name
));
243 throw new Exception(msg
);
247 void saveTagId (Tag tag
) {
249 import std
.conv
: to
;
250 meta
.ids
~= BookMetadata
.Anc(tag
.id
.to
!dstring
, lay
.nextWordIndex
);
254 void putImage (Tag tag
) {
255 if (tag
is null || tag
.href
.length
< 2 || tag
.href
[0] != '#') return;
256 string iid
= tag
.href
[1..$];
257 //writeln("searching for image with href '", iid, "' (", book.images.length, ")");
258 foreach (immutable idx
, ref BookText
.Image img
; book
.images
) {
260 //writeln("image '", img.id, "' found");
261 lay
.putObject(new BookImage(book
, cast(uint)idx
));
267 void putParaContentInternal (Tag ct
, int boldc
=0, int italicc
=0, int underc
=0) {
268 if (ct
is null) return;
269 auto c
= lay
.fontStyle
.color
;
270 scope(exit
) lay
.fontStyle
.color
= c
;
273 case "strong": ++boldc
; lay
.fontStyle
.bold
= true; break;
274 case "emphasis": ++italicc
; lay
.fontStyle
.italic
= true; break;
275 case "image": putImage(ct
); return;
276 case "style": return;
278 if (ct
.href
.length
) {
279 import std
.conv
: to
;
280 meta
.hrefs
~= BookMetadata
.Anc(ct
.href
.to
!dstring
, lay
.nextWordIndex
);
282 lay
.fontStyle
.underline
= true;
283 lay
.fontStyle
.color
= colorTextHref
.asUint
;
286 case "": lay
.put(ct
.text
); return;
290 foreach (Tag tag
; ct
.children
) putParaContentInternal(tag
, boldc
, italicc
, underc
);
292 case "strong": if (--boldc
== 0) lay
.fontStyle
.bold
= false; break;
293 case "emphasis": if (--italicc
== 0) lay
.fontStyle
.italic
= false; break;
294 case "a": if (--underc
== 0) lay
.fontStyle
.underline
= false; break;
300 void putTagContents (Tag ct
) {
301 if (ct
is null) return;
303 foreach (Tag tag
; ct
.children
) putParaContentInternal(tag
);
307 void putParas (Tag ct
) {
309 foreach (Tag tag
; ct
.children
) {
310 if (tag
.name
.length
== 0) continue;
311 if (tag
.name
== "p") putTagContents(tag
);
312 else if (tag
.name
== "empty-line") lay
.endLine();
313 else if (tag
.name
== "text-author") {}
318 void putAuthor (Tag ct
) {
320 foreach (Tag tag
; ct
.children
) {
321 if (tag
.name
.length
== 0) continue;
322 if (tag
.name
== "text-author") {
329 void putCite (Tag ct
) {
331 scope(exit
) lay
.popStyles
;
337 void putEpigraph (Tag ct
) {
339 scope(exit
) lay
.popStyles
;
340 setEpigraphStyle(lay
);
345 void putStanza (Tag ct
) {
347 foreach (Tag tag
; ct
.children
) {
348 if (tag
.name
.length
== 0) continue;
349 if (tag
.name
== "text-author") continue;
350 if (tag
.name
== "title") badTag(tag
, "titles in stanzas are not supported yet");
351 if (tag
.name
== "subtitle") badTag(tag
, "subtitles in stanzas are not supported yet");
352 if (tag
.name
== "epigraph") badTag(tag
, "epigraphs in poems are not supported yet");
353 if (tag
.name
== "date") continue;
354 if (tag
.name
== "v") { putTagContents(tag
); continue; }
359 void putPoem (Tag ct
) {
362 scope(exit
) lay
.popStyles
;
364 // put epigraph (not yet)
365 // put title and subtitle
366 foreach (Tag tag
; ct
.children
) {
367 if (tag
.name
== "title") {
369 scope(exit
) lay
.popStyles
;
371 lay
.endPara(); // space
372 } else if (tag
.name
== "subtitle") {
374 scope(exit
) lay
.popStyles
;
376 lay
.endPara(); // space
379 foreach (Tag tag
; ct
.children
) {
380 if (tag
.name
.length
== 0) continue;
381 if (tag
.name
== "text-author") continue;
382 if (tag
.name
== "title") continue;
383 if (tag
.name
== "subtitle") continue;
384 if (tag
.name
== "epigraph") badTag(tag
, "epigraphs in poems are not supported yet");
385 if (tag
.name
== "date") continue;
386 if (tag
.name
== "stanza") { putStanza(tag
); lay
.put(LayText
.EndParaCh
); continue; }
392 void putSection (Tag sc
) {
393 bool sectionRegistered
= false;
395 void registerSection (Tag tag
) {
396 import std
.conv
: to
;
397 if (sectionRegistered
) return;
398 sectionRegistered
= true;
399 string text
= tag
.textContent
.xstrip
;
400 //lay.sections ~= lay.curWordIndex;
402 while (text
.length
) {
403 char ch
= text
.ptr
[0];
405 if (ch
<= ' ' || ch
== 127) {
406 if (name
.length
== 0) continue;
407 if (ch
!= '\n') ch
= ' ';
409 if (name
[$-1] > ' ') name
~= "\n...";
412 if (name
[$-1] > ' ') name
~= ' ';
419 if (name
.length
== 0) name
= "* * *";
420 meta
.sections
~= BookMetadata
.Anc(name
.to
!dstring
, lay
.nextWordIndex
);
424 foreach (Tag tag
; sc
.children
) {
425 if (tag
.name
.length
== 0) continue;
426 if (tag
.name
== "title") {
428 scope(exit
) lay
.popStyles
;
430 registerSection(tag
);
432 } else if (tag
.name
== "subtitle") {
434 scope(exit
) lay
.popStyles
;
435 setSubtitleStyle(lay
);
436 registerSection(tag
);
438 } else if (tag
.name
== "epigraph") {
440 scope(exit
) lay
.popStyles
;
441 setEpigraphStyle(lay
);
443 } else if (tag
.name
== "section") {
445 } else if (tag
.name
== "p") {
447 } else if (tag
.name
== "image") {
449 scope(exit
) lay
.popStyles
;
450 lay
.lineStyle
.leftpad
= 0;
451 lay
.lineStyle
.rightpad
= 0;
452 lay
.lineStyle
.paraIndent
= 0;
453 lay
.lineStyle
.setCenter
;
456 } else if (tag
.name
== "empty-line") {
458 } else if (tag
.name
== "cite") {
460 } else if (tag
.name
== "table") {
462 scope(exit
) lay
.popStyles
;
463 lay
.fontStyle
.fontsize
+= 8;
464 lay
.lineStyle
.setCenter
;
465 lay
.put("TABLE SKIPPED");
467 } else if (tag
.name
== "poem") {
469 } else if (tag
.name
== "image") {
470 } else if (tag
.name
== "style") {
477 foreach (Tag tag
; book
.content
.children
) {
478 if (tag
.name
== "title") {
480 scope(exit
) lay
.popStyles
;
485 } else if (tag
.name
== "subtitle") {
487 scope(exit
) lay
.popStyles
;
491 } else if (tag
.name
== "epigraph") {
493 } else if (tag
.name
== "section") {
495 scope(exit
) lay
.popStyles
;
498 } else if (tag
.name
== "image") {
509 // ////////////////////////////////////////////////////////////////////////// //
510 private __gshared LayFontStash laf
; // layouter font stash
513 // ////////////////////////////////////////////////////////////////////////// //
514 private void loadFmtFonts () {
515 laf
= new LayFontStash();
517 laf
.addFont("text", textFontName
);
518 laf
.addFont("texti", textiFontName
);
519 laf
.addFont("textb", textbFontName
);
520 laf
.addFont("textz", textzFontName
);
522 laf
.addFont("mono", monoFontName
);
523 laf
.addFont("monoi", monoiFontName
);
524 laf
.addFont("monob", monobFontName
);
525 laf
.addFont("monoz", monozFontName
);
529 // ////////////////////////////////////////////////////////////////////////// //
531 struct ReformatWork
{
532 shared(BookText
) booktext
;
537 struct ReformatWorkComplete
{
539 shared(BookText
) booktext
;
540 shared(LayText
) laytext
;
541 shared(BookMetadata
) meta
;
548 // ////////////////////////////////////////////////////////////////////////// //
549 void reformatThreadFn (Tid ownerTid
) {
554 int newW
= -1, newH
= -1;
561 //{ import std.stdio; writeln("reformat request received..."); }
562 book
= cast(BookText
)w
.booktext
;
563 newFileName
= w
.bookFileName
;
566 if (newW
< 1) newW
= 1;
567 if (newH
< 1) newH
= 1;
573 if (!doQuit
&& newW
> 0 && newH
> 0) {
576 //writeln("loading new book: '", newFileName, "'");
577 book
= loadBook(newFileName
);
580 int maxWidth
= newW
-4-2-BND_SCROLLBAR_WIDTH
-2;
581 if (maxWidth
< 64) maxWidth
= 64;
584 //writeln("layouting...");
585 auto stt
= MonoTime
.currTime
;
586 auto lay
= new LayText(laf
, maxWidth
);
587 lay
.fontStyle
.color
= colorText
.asUint
;
589 auto meta
= book
.formatBook(lay
);
591 auto ett
= MonoTime
.currTime
-stt
;
592 writeln("layouted in ", ett
.total
!"msecs", " milliseconds");
593 auto res
= ReformatWorkComplete(newW
, newH
, cast(shared)book
, cast(shared)lay
, cast(shared)meta
);
595 } catch (Throwable e
) {
596 // here, we are dead and fucked (the exact order doesn't matter)
597 import core
.stdc
.stdlib
: abort
;
598 import core
.stdc
.stdio
: fprintf
, stderr
;
599 import core
.memory
: GC
;
600 import core
.thread
: thread_suspendAll
;
601 GC
.disable(); // yeah
602 thread_suspendAll(); // stop right here, you criminal scum!
603 auto s
= e
.toString();
604 fprintf(stderr
, "\n=== FATAL ===\n%.*s\n", cast(uint)s
.length
, s
.ptr
);
605 abort(); // die, you bitch!
609 send(ownerTid
, QuitWork());