better text styles
[xreader.git] / booktext.d
blobdbcfc48998974e437f48b62f3ed38081bbd2d7e2
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/>.
17 module booktext /*is aliced*/;
19 import std.encoding;
21 import arsd.color;
22 import arsd.png;
23 import arsd.jpeg;
25 import iv.encoding;
26 import iv.nanovg;
27 import iv.saxy;
28 import iv.strex;
29 import iv.utfutil;
30 import iv.vfs;
32 import xlayouter;
35 // ////////////////////////////////////////////////////////////////////////// //
36 struct BookInfo {
37 string author;
38 string title;
39 string seqname;
40 uint seqnum;
41 string diskfile; // not set by `loadBookInfo()`!
45 BookInfo loadBookInfo (const(char)[] fname) {
46 static BookInfo loadFile (VFile fl) {
47 BookInfo res;
48 auto sax = new SaxyEx();
49 bool complete = false;
50 string authorFirst, authorLast;
52 inout(char)[] strip (inout(char)[] s) inout {
53 while (s.length && s.ptr[0] <= ' ') s = s[1..$];
54 while (s.length && s[$-1] <= ' ') s = s[0..$-1];
55 return s;
58 try {
59 // sequence tag
60 sax.onOpen("/FictionBook/description/title-info/sequence", (char[] text, char[][string] attrs) {
61 if (auto sn = "name" in attrs) {
62 res.seqname = strip(*sn).dup;
63 if (auto id = "number" in attrs) {
64 import std.conv : to;
65 res.seqnum = to!uint(*id);
66 if (res.seqnum < 1 || res.seqnum > 8192) res.seqname = null;
68 } else {
69 res.seqname = null;
71 if (res.seqname.length == 0) { res.seqname = null; res.seqnum = 0; }
72 });
74 sax.onClose("/FictionBook/description", (char[] text) {
75 complete = true;
76 throw new Exception("done"); // sorry
77 });
79 // author's first name
80 sax.onContent("/FictionBook/description/title-info/author/first-name", (char[] text) {
81 authorFirst = strip(text).idup;
82 });
83 // author's last name
84 sax.onContent("/FictionBook/description/title-info/author/last-name", (char[] text) {
85 authorLast = strip(text).idup;
86 });
87 // book title
88 sax.onContent("/FictionBook/description/title-info/book-title", (char[] text) {
89 res.title = strip(text).idup;
90 });
92 sax.loadStream(fl);
93 } catch (Exception e) {
94 if (!complete) throw e;
96 if (authorFirst.length == 0) {
97 authorFirst = authorLast;
98 authorLast = null;
99 } else if (authorLast.length != 0) {
100 authorFirst ~= " "~authorLast;
102 if (authorFirst.length == 0 && res.title.length == 0) throw new Exception("no book title found");
103 res.author = authorFirst;
104 return res;
107 import std.path : extension;
108 if (strEquCI(fname.extension, ".zip")) {
109 vfsAddPak(fname);
110 scope(exit) vfsRemovePack(fname);
111 foreach (immutable idx, ref de; vfsFileList) {
112 if (strEquCI(de.name.extension, ".fb2")) return loadFile(vfsOpenFile(de.name));
114 throw new Exception("no fb2 file found in '"~fname.idup~"'");
115 } else {
116 return loadFile(vfsOpenFile(fname));
121 // ////////////////////////////////////////////////////////////////////////// //
122 final class Tag {
123 string name; // empty: text node
124 string id;
125 string href;
126 string text;
127 Tag[] children;
128 Tag parent;
129 //Tag prevSibling;
130 //Tag nextSibling;
132 @property inout(Tag) firstChild () inout pure nothrow @trusted @nogc { pragma(inline, true); return (children.length ? children.ptr[0] : null); }
133 @property inout(Tag) lastChild () inout pure nothrow @trusted @nogc { pragma(inline, true); return (children.length ? children[$-1] : null); }
135 string textContent () const {
136 if (name.length == 0) return text;
137 string res;
138 foreach (const Tag t; children) res ~= t.textContent;
139 return res;
142 string toStringNice (int indent=0) const {
143 string res;
144 void addIndent () { foreach (immutable _; 0..indent) res ~= ' '; }
145 void newLine () { res ~= '\n'; foreach (immutable _; 0..indent) res ~= ' '; }
146 if (name.length == 0) return text;
147 res ~= '<';
148 if (children.length == 0) res ~= '/';
149 res ~= name;
150 if (id.length) { res ~= " id="; res ~= id; }
151 if (href.length) { res ~= " href="; res ~= href; }
152 res ~= '>';
153 if (children.length) {
154 if (indent >= 0) indent += 2;
155 foreach (const Tag cc; children) {
156 if (indent >= 0) newLine();
157 res ~= cc.toStringNice(indent);
159 if (indent >= 0) indent -= 2;
160 if (indent >= 0) newLine();
161 res ~= "</";
162 res ~= name;
163 res ~= ">";
165 return res;
168 override string toString () const { return toStringNice(int.min); }
172 // ////////////////////////////////////////////////////////////////////////// //
173 final class BookText {
174 string authorFirst;
175 string authorLast;
176 string title;
177 string sequence;
178 uint seqnum;
180 Tag content; // body
182 this (const(char)[] fname) { loadFile(fname); }
183 this (VFile fl) { loadFile(fl); }
185 private:
186 void loadFile (VFile fl) {
187 auto sax = new SaxyEx();
189 string norm (const(char)[] s) {
190 string res;
191 foreach (char ch; s) {
192 if (ch <= ' ' || ch == 127) {
193 if (res.length && res[$-1] > ' ') res ~= ch;
194 } else {
195 res ~= ch;
198 return res;
201 // sequence tag
202 sax.onOpen("/FictionBook/description/title-info/sequence", (char[] text, char[][string] attrs) {
203 if (auto sn = "name" in attrs) {
204 sequence = (*sn).dup;
205 if (auto id = "number" in attrs) {
206 import std.conv : to;
207 seqnum = to!uint(*id);
208 if (seqnum < 1 || seqnum > 8192) sequence = null;
210 } else {
211 sequence = null;
213 if (sequence.length == 0) { sequence = null; seqnum = 0; }
216 // author's first name
217 sax.onContent("/FictionBook/description/title-info/author/first-name", (char[] text) {
218 authorFirst = norm(text);
220 // author's last name
221 sax.onContent("/FictionBook/description/title-info/author/last-name", (char[] text) {
222 authorLast = norm(text);
224 // book title
225 sax.onContent("/FictionBook/description/title-info/book-title", (char[] text) {
226 title = norm(text);
229 Tag[] tagStack;
231 sax.onOpen("/FictionBook/body", (char[] text) {
232 if (content is null) {
233 content = new Tag();
234 content.name = "body";
236 tagStack ~= content;
239 sax.onClose("/FictionBook/body", (char[] text) {
240 if (content is null) assert(0, "wtf?!");
241 tagStack.length -= 1;
242 tagStack.assumeSafeAppend;
245 sax.onOpen("/FictionBook/body/+", (char[] text, char[][string] attrs) {
246 auto tag = new Tag();
247 tag.name = text.idup;
248 tag.parent = tagStack[$-1];
250 if (auto ls = tag.parent.lastChild) {
251 ls.nextSibling = tag;
252 tag.prevSibling = ls;
255 tag.parent.children ~= tag;
256 if (auto p = "id" in attrs) tag.id = norm(*p);
257 if (auto p = "href" in attrs) tag.href = norm(*p);
258 tagStack ~= tag;
261 sax.onClose("/FictionBook/body/+", (char[] text) {
262 tagStack.length -= 1;
263 tagStack.assumeSafeAppend;
266 sax.onContent("/FictionBook/body/+", (char[] text) {
267 auto tag = new Tag();
268 tag.name = null;
269 tag.parent = tagStack[$-1];
271 if (auto ls = tag.parent.lastChild) {
272 ls.nextSibling = tag;
273 tag.prevSibling = ls;
276 tag.parent.children ~= tag;
277 tag.text ~= text.idup;
280 sax.loadStream(fl);
282 if (content is null) {
283 content = new Tag();
284 content.name = "body";
288 void loadFile (const(char)[] fname) {
289 import std.path : extension;
290 if (strEquCI(fname.extension, ".zip")) {
291 vfsAddPak(fname);
292 scope(exit) vfsRemovePack(fname);
293 foreach (immutable idx, ref de; vfsFileList) {
294 if (strEquCI(de.name.extension, ".fb2")) { loadFile(vfsOpenFile(de.name)); return; }
296 throw new Exception("no fb2 file found in '"~fname.idup~"'");
297 } else {
298 loadFile(vfsOpenFile(fname));