1 /*******************************************************
2 
3  CoolReader Engine
4 
5  lvdocview.cpp:  XML DOM tree rendering tools
6 
7  (c) Vadim Lopatin, 2000-2009
8  This source code is distributed under the terms of
9  GNU General Public License
10  See LICENSE file for details
11 
12  *******************************************************/
13 
14 #include "../include/crsetup.h"
15 #include "../include/fb2def.h"
16 #include "../include/lvdocview.h"
17 #include "../include/rtfimp.h"
18 
19 #include "../include/lvstyles.h"
20 #include "../include/lvrend.h"
21 #include "../include/lvstsheet.h"
22 #include "../include/textlang.h"
23 
24 #include "../include/wolutil.h"
25 #include "../include/crtxtenc.h"
26 #include "../include/crtrace.h"
27 #include "../include/epubfmt.h"
28 #include "../include/chmfmt.h"
29 #include "../include/wordfmt.h"
30 #include "../include/pdbfmt.h"
31 #include "../include/fb3fmt.h"
32 #include "../include/docxfmt.h"
33 #include "../include/odtfmt.h"
34 
35 /// to show page bounds rectangles
36 //#define SHOW_PAGE_RECT
37 
38 /// uncomment to save copy of loaded document to file
39 //#define SAVE_COPY_OF_LOADED_DOCUMENT
40 
41 // TESTING GRAYSCALE MODE
42 #if 0
43 #undef COLOR_BACKBUFFER
44 #define COLOR_BACKBUFFER 0
45 #undef GRAY_BACKBUFFER_BITS
46 #define GRAY_BACKBUFFER_BITS 4
47 #endif
48 
49 
50 #if 0
51 #define REQUEST_RENDER(txt) {CRLog::trace("request render from "  txt); requestRender();}
52 #else
53 #define REQUEST_RENDER(txt) requestRender();
54 #endif
55 
56 #if 0
57 #define CHECK_RENDER(txt) {CRLog::trace("LVDocView::checkRender() - from " txt); checkRender();}
58 #else
59 #define CHECK_RENDER(txt) checkRender();
60 #endif
61 
62 /// to avoid showing title/author if coverpage image present
63 #define NO_TEXT_IN_COVERPAGE
64 
65 const char
66 		* def_stylesheet =
67 				"image { text-align: center; text-indent: 0px } \n"
68 					"empty-line { height: 1em; } \n"
69 					"sub { vertical-align: sub; font-size: 70% }\n"
70 					"sup { vertical-align: super; font-size: 70% }\n"
71 					"body > image, section > image { text-align: center; margin-before: 1em; margin-after: 1em }\n"
72 					"p > image { display: inline }\n"
73 					"a { vertical-align: super; font-size: 80% }\n"
74 					"p { margin-top:0em; margin-bottom: 0em }\n"
75 					"text-author { font-weight: bold; font-style: italic; margin-left: 5%}\n"
76 					"empty-line { height: 1em }\n"
77 					"epigraph { margin-left: 30%; margin-right: 4%; text-align: left; text-indent: 1px; font-style: italic; margin-top: 15px; margin-bottom: 25px; font-family: Times New Roman, serif }\n"
78 					"strong, b { font-weight: bold }\n"
79 					"emphasis, i { font-style: italic }\n"
80 					"title { text-align: center; text-indent: 0px; font-size: 130%; font-weight: bold; margin-top: 10px; margin-bottom: 10px; font-family: Times New Roman, serif }\n"
81 					"subtitle { text-align: center; text-indent: 0px; font-size: 150%; margin-top: 10px; margin-bottom: 10px }\n"
82 					"title { page-break-before: always; page-break-inside: avoid; page-break-after: avoid; }\n"
83 					"body { text-align: justify; text-indent: 2em }\n"
84 					"cite { margin-left: 30%; margin-right: 4%; text-align: justyfy; text-indent: 0px;  margin-top: 20px; margin-bottom: 20px; font-family: Times New Roman, serif }\n"
85 					"td, th { text-indent: 0px; font-size: 80%; margin-left: 2px; margin-right: 2px; margin-top: 2px; margin-bottom: 2px; text-align: left; padding: 2px }\n"
86 					"th { font-weight: bold }\n"
87 					"table > caption { padding: 2px; text-indent: 0px; font-size: 80%; font-weight: bold; text-align: left; background-color: #AAAAAA }\n"
88 					"table { border-spacing:2px;}\n"
89 					"body[name=\"notes\"] { font-size: 70%; }\n"
90 					"body[name=\"notes\"]  section[id] { text-align: left; }\n"
91 					"body[name=\"notes\"]  section[id] title { display: block; text-align: left; font-size: 110%; font-weight: bold; page-break-before: auto; page-break-inside: auto; page-break-after: auto; }\n"
92 					"body[name=\"notes\"]  section[id] title p { text-align: left; display: inline }\n"
93 					"body[name=\"notes\"]  section[id] empty-line { display: inline }\n"
94 					"code, pre { display: block; white-space: pre; font-family: \"Courier New\", monospace }\n";
95 
96 static const char * DEFAULT_FONT_NAME = "Arial, DejaVu Sans"; //Times New Roman";
97 static const char * DEFAULT_STATUS_FONT_NAME =
98 		"Arial Narrow, Arial, DejaVu Sans"; //Times New Roman";
99 static css_font_family_t DEFAULT_FONT_FAMILY = css_ff_sans_serif;
100 //    css_ff_serif,
101 //    css_ff_sans_serif,
102 //    css_ff_cursive,
103 //    css_ff_fantasy,
104 //    css_ff_monospace
105 
106 #ifdef LBOOK
107 #define INFO_FONT_SIZE      22
108 #else
109 #define INFO_FONT_SIZE      22
110 #endif
111 
112 #ifndef MIN_STATUS_FONT_SIZE
113 #define MIN_STATUS_FONT_SIZE 8
114 #endif
115 
116 #ifndef MAX_STATUS_FONT_SIZE
117 #define MAX_STATUS_FONT_SIZE 255
118 #endif
119 
120 // Fallback defines, see crsetup.h
121 #ifndef SCREEN_SIZE_MIN
122 #define SCREEN_SIZE_MIN 80
123 #endif
124 
125 #ifndef SCREEN_SIZE_MAX
126 #define SCREEN_SIZE_MAX 32767
127 #endif
128 
129 #if defined(__SYMBIAN32__)
130 #include <e32std.h>
131 #define DEFAULT_PAGE_MARGIN 2
132 #else
133 #ifdef LBOOK
134 #define DEFAULT_PAGE_MARGIN      8
135 #else
136 #define DEFAULT_PAGE_MARGIN      12
137 #endif
138 #endif
139 
140 #define HEADER_MARGIN       4
141 #define PAGE_HEADER_POS_NONE    0
142 #define PAGE_HEADER_POS_TOP     1
143 #define PAGE_HEADER_POS_BOTTOM  2
144 
145 /// minimum EM width of page (prevents show two pages for windows that not enougn wide)
146 #define MIN_EM_PER_PAGE     20
147 
148 static int def_font_sizes[] = { 18, 20, 22, 24, 29, 33, 39, 44 };
149 
LVDocView(int bitsPerPixel,bool noDefaultDocument)150 LVDocView::LVDocView(int bitsPerPixel, bool noDefaultDocument) :
151 	m_bitsPerPixel(bitsPerPixel), m_dx(400), m_dy(200), _pos(0), _page(0),
152 			_posIsSet(false), m_battery_state(CR_BATTERY_STATE_NO_BATTERY)
153 #if (LBOOK==1)
154 			, m_requested_font_size(32)
155 #elif defined(__SYMBIAN32__)
156 			, m_requested_font_size(30)
157 #else
158 			, m_requested_font_size(24)
159 #endif
160 			, m_status_font_size(INFO_FONT_SIZE),
161 			m_def_interline_space(100),
162 #if USE_LIMITED_FONT_SIZES_SET
163 			m_font_sizes(def_font_sizes, sizeof(def_font_sizes) / sizeof(int)),
164 			m_font_sizes_cyclic(false),
165 #else
166 			m_min_font_size(6),
167 			m_max_font_size(72),
168 #endif
169 			m_is_rendered(false),
170 			m_view_mode(1 ? DVM_PAGES : DVM_SCROLL) // choose 0/1
171 			/*
172 			 , m_drawbuf(100, 100
173 			 #if COLOR_BACKBUFFER==0
174 			 , GRAY_BACKBUFFER_BITS
175 			 #endif
176 			 )
177 			 */
178 			, m_stream(NULL), m_doc(NULL), m_stylesheet(def_stylesheet),
179             m_backgroundTiled(true),
180             m_stylesheetNeedsUpdate(true),
181             m_highlightBookmarks(1),
182 			m_pageMargins(DEFAULT_PAGE_MARGIN,
183 					DEFAULT_PAGE_MARGIN / 2 /*+ INFO_FONT_SIZE + 4 */,
184 					DEFAULT_PAGE_MARGIN, DEFAULT_PAGE_MARGIN / 2),
185             m_pagesVisible(2), m_pagesVisibleOverride(0), m_pageHeaderPos(PAGE_HEADER_POS_TOP), m_pageHeaderInfo(PGHDR_PAGE_NUMBER
186 #ifndef LBOOK
187 					| PGHDR_CLOCK
188 #endif
189 					| PGHDR_BATTERY | PGHDR_PAGE_COUNT | PGHDR_AUTHOR
190 					| PGHDR_TITLE), m_showCover(true)
191 #if CR_INTERNAL_PAGE_ORIENTATION==1
192 			, m_rotateAngle(CR_ROTATE_ANGLE_0)
193 #endif
194 			, m_section_bounds_externally_updated(false)
195 			, m_section_bounds_valid(false), m_doc_format(doc_format_none),
196 			m_callback(NULL), m_swapDone(false), m_drawBufferBits(
197 					GRAY_BACKBUFFER_BITS) {
198 #if (COLOR_BACKBUFFER==1)
199 	m_backgroundColor = 0xFFFFFF;
200 	m_textColor = 0x000000;
201 #else
202 #if (GRAY_INVERSE==1)
203 	m_backgroundColor = 0;
204 	m_textColor = 3;
205 #else
206 	m_backgroundColor = 3;
207 	m_textColor = 0;
208 #endif
209 #endif
210 	m_statusColor = 0xFF000000;
211 	m_defaultFontFace = lString8(DEFAULT_FONT_NAME);
212 	m_statusFontFace = lString8(DEFAULT_STATUS_FONT_NAME);
213 	m_props = LVCreatePropsContainer();
214 	m_doc_props = LVCreatePropsContainer();
215 	propsUpdateDefaults( m_props);
216 
217 	//m_drawbuf.Clear(m_backgroundColor);
218 
219     if (!noDefaultDocument)
220         // NOLINTNEXTLINE: Call to virtual function during construction
221         createDefaultDocument(cs32("No document"), lString32(
222                     U"Welcome to CoolReader! Please select file to open"));
223 
224     m_font_size = scaleFontSizeForDPI(m_requested_font_size);
225     gRootFontSize = m_font_size; // stored as global (for 'rem' css unit)
226     m_font = fontMan->GetFont(m_font_size, 400, false, DEFAULT_FONT_FAMILY,
227 			m_defaultFontFace);
228 	m_infoFont = fontMan->GetFont(m_status_font_size, 700, false,
229 			DEFAULT_FONT_FAMILY, m_statusFontFace);
230 #ifdef ANDROID
231 	//m_batteryFont = fontMan->GetFont( 20, 700, false, DEFAULT_FONT_FAMILY, m_statusFontFace );
232 #endif
233 
234 }
235 
~LVDocView()236 LVDocView::~LVDocView() {
237 	Clear();
238 }
239 
getPageSkin()240 CRPageSkinRef LVDocView::getPageSkin() {
241 	return _pageSkin;
242 }
243 
setPageSkin(CRPageSkinRef skin)244 void LVDocView::setPageSkin(CRPageSkinRef skin) {
245 	_pageSkin = skin;
246 }
247 
248 /// get text format options
getTextFormatOptions()249 txt_format_t LVDocView::getTextFormatOptions() {
250     return m_doc && m_doc->getDocFlag(DOC_FLAG_PREFORMATTED_TEXT) ? txt_format_pre
251 			: txt_format_auto;
252 }
253 
254 /// set text format options
setTextFormatOptions(txt_format_t fmt)255 void LVDocView::setTextFormatOptions(txt_format_t fmt) {
256 	txt_format_t m_text_format = getTextFormatOptions();
257 	CRLog::trace("setTextFormatOptions( %d ), current state = %d", (int) fmt,
258 			(int) m_text_format);
259 	if (m_text_format == fmt)
260 		return; // no change
261 	m_props->setBool(PROP_TXT_OPTION_PREFORMATTED, (fmt == txt_format_pre));
262 	if (m_doc) // not when noDefaultDocument=true
263 		m_doc->setDocFlag(DOC_FLAG_PREFORMATTED_TEXT, (fmt == txt_format_pre));
264 	if (getDocFormat() == doc_format_txt) {
265 		requestReload();
266 		CRLog::trace(
267 				"setTextFormatOptions() -- new value set, reload requested");
268 	} else {
269 		CRLog::trace(
270 				"setTextFormatOptions() -- doc format is %d, reload is necessary for %d only",
271 				(int) getDocFormat(), (int) doc_format_txt);
272 	}
273 }
274 
275 /// invalidate document data, request reload
requestReload()276 void LVDocView::requestReload() {
277 	if (getDocFormat() != doc_format_txt)
278 		return; // supported for text files only
279 	if (m_callback) {
280         if (m_callback->OnRequestReload()) {
281             CRLog::info("LVDocView::requestReload() : reload request will be processed by external code");
282             return;
283         }
284         m_callback->OnLoadFileStart(m_doc_props->getStringDef(
285 				DOC_PROP_FILE_NAME, ""));
286 	}
287 	if (m_stream.isNull() && isDocumentOpened()) {
288 		savePosition();
289 		CRFileHist * hist = getHistory();
290 		if (!hist || hist->getRecords().length() <= 0)
291 			return;
292         //lString32 fn = hist->getRecords()[0]->getFilePathName();
293         lString32 fn = m_filename;
294 		bool res = LoadDocument(fn.c_str());
295 		if (res) {
296 			//swapToCache();
297 			restorePosition();
298 		} else {
299             createDefaultDocument(lString32::empty_str, lString32(
300 					"Error while opening document ") + fn);
301 		}
302 		checkRender();
303 		return;
304 	}
305 	ParseDocument();
306 	// TODO: save position
307 	checkRender();
308 }
309 
310 /// returns true if document is opened
isDocumentOpened()311 bool LVDocView::isDocumentOpened() {
312 	return m_doc && m_doc->getRootNode() && !m_doc_props->getStringDef(
313 			DOC_PROP_FILE_NAME, "").empty();
314 }
315 
316 /// rotate rectangle by current angle, winToDoc==false for doc->window translation, true==ccw
rotateRect(lvRect & rc,bool winToDoc)317 lvRect LVDocView::rotateRect(lvRect & rc, bool winToDoc) {
318 #if CR_INTERNAL_PAGE_ORIENTATION==1
319 	lvRect rc2;
320 	cr_rotate_angle_t angle = m_rotateAngle;
321 	if ( winToDoc )
322 	angle = (cr_rotate_angle_t)((4 - (int)angle) & 3);
323 	switch ( angle ) {
324 		case CR_ROTATE_ANGLE_0:
325 		rc2 = rc;
326 		break;
327 		case CR_ROTATE_ANGLE_90:
328 		/*
329 		 . . . . . .      . . . . . . . .
330 		 . . . . . .      . . . . . 1 . .
331 		 . 1 . . . .      . . . . . . . .
332 		 . . . . . .  ==> . . . . . . . .
333 		 . . . . . .      . 2 . . . . . .
334 		 . . . . . .      . . . . . . . .
335 		 . . . . 2 .
336 		 . . . . . .
337 
338 		 */
339 		rc2.left = m_dy - rc.bottom - 1;
340 		rc2.right = m_dy - rc.top - 1;
341 		rc2.top = rc.left;
342 		rc2.bottom = rc.right;
343 		break;
344 		case CR_ROTATE_ANGLE_180:
345 		rc2.left = m_dx - rc.left - 1;
346 		rc2.right = m_dx - rc.right - 1;
347 		rc2.top = m_dy - rc.top - 1;
348 		rc2.bottom = m_dy - rc.bottom - 1;
349 		break;
350 		case CR_ROTATE_ANGLE_270:
351 		/*
352 		 . . . . . .      . . . . . . . .
353 		 . . . . . .      . 1 . . . . . .
354 		 . . . . 2 .      . . . . . . . .
355 		 . . . . . .  <== . . . . . . . .
356 		 . . . . . .      . . . . . 2 . .
357 		 . . . . . .      . . . . . . . .
358 		 . 1 . . . .
359 		 . . . . . .
360 
361 		 */
362 		rc2.left = rc.top;
363 		rc2.right = rc.bottom;
364 		rc2.top = m_dx - rc.right - 1;
365 		rc2.bottom = m_dx - rc.left - 1;
366 		break;
367 	}
368 	return rc2;
369 #else
370 	return rc;
371 #endif
372 }
373 
374 /// rotate point by current angle, winToDoc==false for doc->window translation, true==ccw
rotatePoint(lvPoint & pt,bool winToDoc)375 lvPoint LVDocView::rotatePoint(lvPoint & pt, bool winToDoc) {
376 #if CR_INTERNAL_PAGE_ORIENTATION==1
377 	lvPoint pt2;
378 	cr_rotate_angle_t angle = m_rotateAngle;
379 	if ( winToDoc )
380 	angle = (cr_rotate_angle_t)((4 - (int)angle) & 3);
381 	switch ( angle ) {
382 		case CR_ROTATE_ANGLE_0:
383 		pt2 = pt;
384 		break;
385 		case CR_ROTATE_ANGLE_90:
386 		/*
387 		 . . . . . .      . . . . . . . .
388 		 . . . . . .      . . . . . 1 . .
389 		 . 1 . . . .      . . . . . . . .
390 		 . . . . . .  ==> . . . . . . . .
391 		 . . . . . .      . 2 . . . . . .
392 		 . . . . . .      . . . . . . . .
393 		 . . . . 2 .
394 		 . . . . . .
395 
396 		 */
397 		pt2.y = pt.x;
398 		pt2.x = m_dx - pt.y - 1;
399 		break;
400 		case CR_ROTATE_ANGLE_180:
401 		pt2.y = m_dy - pt.y - 1;
402 		pt2.x = m_dx - pt.x - 1;
403 		break;
404 		case CR_ROTATE_ANGLE_270:
405 		/*
406 		 . . . . . .      . . . . . . . .
407 		 . . . . . .      . 1 . . . . . .
408 		 . . . . 2 .      . . . . . . . .
409 		 . . . . . .  <== . . . . . . . .
410 		 . . . . . .      . . . . . 2 . .
411 		 . . . . . .      . . . . . . . .
412 		 . 1 . . . .
413 		 . . . . . .
414 
415 		 */
416 		pt2.y = m_dy - pt.x - 1;
417 		pt2.x = pt.y;
418 		break;
419 	}
420 	return pt2;
421 #else
422 	return pt;
423 #endif
424 }
425 
426 /// update page margins based on current settings
updatePageMargins()427 void LVDocView::updatePageMargins() {
428     lvRect rc = getPageMargins();
429     rc.left = m_props->getIntDef(PROP_PAGE_MARGIN_LEFT, rc.left);
430     rc.top = m_props->getIntDef(PROP_PAGE_MARGIN_TOP, rc.top);
431     rc.right = m_props->getIntDef(PROP_PAGE_MARGIN_RIGHT, rc.right);
432     rc.bottom = m_props->getIntDef(PROP_PAGE_MARGIN_BOTTOM, rc.bottom);
433     int maxhmargin = m_dx / 5;
434     int maxvmargin = m_dy / 5;
435     if (rc.left > maxhmargin)
436         rc.left = maxhmargin;
437     if (rc.right > maxhmargin)
438         rc.right = maxhmargin;
439     if (rc.top > maxvmargin)
440         rc.top = maxvmargin;
441     if (rc.bottom > maxvmargin)
442         rc.bottom = maxvmargin;
443     setPageMargins(rc);
444 }
445 
446 /// sets page margins
setPageMargins(lvRect rc)447 void LVDocView::setPageMargins(lvRect rc) {
448     if (m_pageMargins.left + m_pageMargins.right != rc.left + rc.right
449             || m_pageMargins.top + m_pageMargins.bottom != rc.top + rc.bottom) {
450 
451         m_pageMargins = rc;
452         updateLayout();
453         REQUEST_RENDER("setPageMargins")
454     } else {
455 		clearImageCache();
456         m_pageMargins = rc;
457     }
458 }
459 
setPageHeaderPosition(int pos)460 void LVDocView::setPageHeaderPosition( int pos ) {
461 	if (m_pageHeaderPos == pos)
462 		return;
463 	LVLock lock(getMutex());
464 	int oldH = getPageHeaderHeight();
465 	m_pageHeaderPos = pos;
466 	int h = getPageHeaderHeight();
467 	if (h != oldH) {
468 		REQUEST_RENDER("setPageHeaderPosition")
469 	} else {
470 		clearImageCache();
471 	}
472 }
473 
setPageHeaderInfo(int hdrFlags)474 void LVDocView::setPageHeaderInfo(int hdrFlags) {
475 	if (m_pageHeaderInfo == hdrFlags)
476 		return;
477 	LVLock lock(getMutex());
478 	int oldH = getPageHeaderHeight();
479 	m_pageHeaderInfo = hdrFlags;
480 	int h = getPageHeaderHeight();
481 	if (h != oldH) {
482         REQUEST_RENDER("setPageHeaderInfo")
483 	} else {
484 		clearImageCache();
485 	}
486 }
487 
mergeCssMacros(CRPropRef props)488 lString32 mergeCssMacros(CRPropRef props) {
489     lString8 res = lString8::empty_str;
490     for (int i=0; i<props->getCount(); i++) {
491     	lString8 n(props->getName(i));
492     	if (n.endsWith(".day") || n.endsWith(".night"))
493     		continue;
494         lString32 v = props->getValue(i);
495         if (!v.empty()) {
496             if (v.lastChar() != ';')
497                 v.append(1, ';');
498             if (v.lastChar() != ' ')
499                 v.append(1, ' ');
500             res.append(UnicodeToUtf8(v));
501         }
502     }
503     //CRLog::trace("merged: %s", res.c_str());
504     return Utf8ToUnicode(res);
505 }
506 
substituteCssMacros(lString8 src,CRPropRef props)507 lString8 substituteCssMacros(lString8 src, CRPropRef props) {
508     lString8 res = lString8(src.length());
509     const char * s = src.c_str();
510     for (; *s; s++) {
511         if (*s == '$') {
512             const char * s2 = s + 1;
513             bool err = false;
514             for (; *s2 && *s2 != ';' && *s2 != '}' &&  *s2 != ' ' &&  *s2 != '\r' &&  *s2 != '\n' &&  *s2 != '\t'; s2++) {
515                 char ch = *s2;
516                 if (ch != '.' && ch != '-' && (ch < 'a' || ch > 'z')) {
517                     err = true;
518                 }
519             }
520             if (!err) {
521                 // substitute variable
522                 lString8 prop(s + 1, (lvsize_t)(s2 - s - 1));
523                 lString32 v;
524                 // $styles.stylename.all will merge all properties like styles.stylename.*
525                 if (prop.endsWith(".all")) {
526                     // merge whole branch
527                     v = mergeCssMacros(props->getSubProps(prop.substr(0, prop.length() - 3).c_str()));
528                     //CRLog::trace("merged %s = %s", prop.c_str(), LCSTR(v));
529                 } else {
530                     // single property
531                     props->getString(prop.c_str(), v);
532                     if (!v.empty()) {
533                         if (v.lastChar() != ';')
534                             v.append(1, ';');
535                         if (v.lastChar() != ' ')
536                             v.append(1, ' ');
537                     }
538                 }
539                 if (!v.empty()) {
540                     res.append(UnicodeToUtf8(v));
541                 } else {
542                     //CRLog::trace("CSS macro not found: %s", prop.c_str());
543                 }
544             }
545             s = s2;
546         } else {
547             res.append(1, *s);
548         }
549     }
550     return res;
551 }
552 
553 /// set document stylesheet text
setStyleSheet(lString8 css_text)554 void LVDocView::setStyleSheet(lString8 css_text) {
555 	LVLock lock(getMutex());
556     REQUEST_RENDER("setStyleSheet")
557     //CRLog::trace("LVDocView::setStyleSheet()");
558     m_stylesheet = css_text;
559     m_stylesheetNeedsUpdate = true;
560 }
561 
updateDocStyleSheet()562 void LVDocView::updateDocStyleSheet() {
563     // Don't skip this when document is not yet rendered (or is being re-rendered)
564     if (m_is_rendered && !m_stylesheetNeedsUpdate)
565         return;
566     CRPropRef p = m_props->getSubProps("styles.");
567     m_doc->setStyleSheet(substituteCssMacros(m_stylesheet, p).c_str(), true);
568     m_stylesheetNeedsUpdate = false;
569 }
570 
Clear()571 void LVDocView::Clear() {
572 	{
573 		LVLock lock(getMutex());
574 		if (m_doc)
575 			delete m_doc;
576 		m_doc = NULL;
577 		m_doc_props->clear();
578 		if (!m_stream.isNull())
579 			m_stream.Clear();
580 		if (!m_container.isNull())
581 			m_container.Clear();
582 		if (!m_arc.isNull())
583 			m_arc.Clear();
584 		_posBookmark = ldomXPointer();
585 		m_is_rendered = false;
586 		m_swapDone = false;
587 		_pos = 0;
588 		_page = 0;
589 		_posIsSet = false;
590 		m_cursorPos.clear();
591 		m_filename.clear();
592 		m_section_bounds_valid = false;
593 	}
594 	clearImageCache();
595 	_navigationHistory.clear();
596 	// Also drop font instances from previous document (see
597 	// lvtinydom.cpp ldomDocument::render() for the reason)
598 	fontMan->gc();
599 	fontMan->gc();
600 }
601 
602 /// invalidate image cache, request redraw
clearImageCache()603 void LVDocView::clearImageCache() {
604 #if CR_ENABLE_PAGE_IMAGE_CACHE==1
605 	m_imageCache.clear();
606 #endif
607 	if (m_callback != NULL)
608 		m_callback->OnImageCacheClear();
609 }
610 
611 /// invalidate formatted data, request render
requestRender()612 void LVDocView::requestRender() {
613 	LVLock lock(getMutex());
614 	if (!m_doc) // nothing to render when noDefaultDocument=true
615 		return;
616 	m_is_rendered = false;
617 	clearImageCache();
618 	if (m_doc)
619 		m_doc->clearRendBlockCache();
620 }
621 
622 /// render document, if not rendered
checkRender()623 void LVDocView::checkRender() {
624 	if (!m_is_rendered) {
625 		LVLock lock(getMutex());
626 		CRLog::trace("LVDocView::checkRender() : render is required");
627 		Render();
628 		clearImageCache();
629 		m_is_rendered = true;
630 		_posIsSet = false;
631 		//CRLog::trace("LVDocView::checkRender() compeleted");
632 	}
633 }
634 
635 /// ensure current position is set to current bookmark value
checkPos()636 void LVDocView::checkPos() {
637     CHECK_RENDER("checkPos()");
638 	if (_posIsSet)
639 		return;
640 	_posIsSet = true;
641 	LVLock lock(getMutex());
642 	if (_posBookmark.isNull()) {
643 		if (isPageMode()) {
644 			goToPage(0);
645 		} else {
646 			SetPos(0, false);
647 		}
648 	} else {
649 		if (isPageMode()) {
650 			int p = getBookmarkPage(_posBookmark);
651             goToPage(p, false);
652 		} else {
653 			//CRLog::trace("checkPos() _posBookmark node=%08X offset=%d", (unsigned)_posBookmark.getNode(), (int)_posBookmark.getOffset());
654 			lvPoint pt = _posBookmark.toPoint();
655 			SetPos(pt.y, false);
656 		}
657 	}
658 }
659 
660 #if CR_ENABLE_PAGE_IMAGE_CACHE==1
661 /// returns true if current page image is ready
IsDrawed()662 bool LVDocView::IsDrawed()
663 {
664 	return isPageImageReady( 0 );
665 }
666 
667 /// returns true if page image is available (0=current, -1=prev, 1=next)
isPageImageReady(int delta)668 bool LVDocView::isPageImageReady( int delta )
669 {
670 	if ( !m_is_rendered || !_posIsSet )
671 	return false;
672 	LVDocImageRef ref;
673 	if ( isPageMode() ) {
674 		int p = _page;
675 		if ( delta<0 )
676 		p--;
677 		else if ( delta>0 )
678 		p++;
679 		//CRLog::trace("getPageImage: checking cache for page [%d] (delta=%d)", offset, delta);
680 		ref = m_imageCache.get( -1, p );
681 	} else {
682 		int offset = _pos;
683 		if ( delta<0 )
684 		offset = getPrevPageOffset();
685 		else if ( delta>0 )
686 		offset = getNextPageOffset();
687 		//CRLog::trace("getPageImage: checking cache for page [%d] (delta=%d)", offset, delta);
688 		ref = m_imageCache.get( offset, -1 );
689 	}
690 	return ( !ref.isNull() );
691 }
692 
693 /// get page image
getPageImage(int delta)694 LVDocImageRef LVDocView::getPageImage( int delta )
695 {
696 	checkPos();
697 	// find existing object in cache
698 	LVDocImageRef ref;
699 	int p = -1;
700 	int offset = -1;
701 	if ( isPageMode() ) {
702 		p = _page;
703 		if ( delta<0 )
704 		p--;
705 		else if ( delta>0 )
706 		p++;
707 		if ( p<0 || p>=m_pages.length() )
708 		return ref;
709 		ref = m_imageCache.get( -1, p );
710 		if ( !ref.isNull() ) {
711 			//CRLog::trace("getPageImage: + page [%d] found in cache", offset);
712 			return ref;
713 		}
714 	} else {
715 		offset = _pos;
716 		if ( delta<0 )
717 		offset = getPrevPageOffset();
718 		else if ( delta>0 )
719 		offset = getNextPageOffset();
720 		//CRLog::trace("getPageImage: checking cache for page [%d] (delta=%d)", offset, delta);
721 		ref = m_imageCache.get( offset, -1 );
722 		if ( !ref.isNull() ) {
723 			//CRLog::trace("getPageImage: + page [%d] found in cache", offset);
724 			return ref;
725 		}
726 	}
727 	while ( ref.isNull() ) {
728 		//CRLog::trace("getPageImage: - page [%d] not found, force rendering", offset);
729 		cachePageImage( delta );
730 		ref = m_imageCache.get( offset, p );
731 	}
732 	//CRLog::trace("getPageImage: page [%d] is ready", offset);
733 	return ref;
734 }
735 
736 class LVDrawThread : public LVThread {
737 	LVDocView * _view;
738 	int _offset;
739 	int _page;
740 	LVRef<LVDrawBuf> _drawbuf;
741 public:
LVDrawThread(LVDocView * view,int offset,int page,LVRef<LVDrawBuf> drawbuf)742 	LVDrawThread( LVDocView * view, int offset, int page, LVRef<LVDrawBuf> drawbuf )
743 	: _view(view), _offset(offset), _page(page), _drawbuf(drawbuf)
744 	{
745 		start();
746 	}
run()747 	virtual void run()
748 	{
749 		//CRLog::trace("LVDrawThread::run() offset==%d", _offset);
750 		_view->Draw( *_drawbuf, _offset, _page, true );
751 		//_drawbuf->Rotate( _view->GetRotateAngle() );
752 	}
753 };
754 #endif
755 
756 /// draw current page to specified buffer
Draw(LVDrawBuf & drawbuf,bool autoResize)757 void LVDocView::Draw(LVDrawBuf & drawbuf, bool autoResize) {
758 	checkPos();
759 	int offset = -1;
760 	int p = -1;
761 	if (isPageMode()) {
762 		p = _page;
763 		if (p < 0 || p >= m_pages.length())
764 			return;
765 	} else {
766 		offset = _pos;
767 	}
768 	//CRLog::trace("Draw() : calling Draw(buf(%d x %d), %d, %d, false)",
769 	//		drawbuf.GetWidth(), drawbuf.GetHeight(), offset, p);
770 	Draw(drawbuf, offset, p, false, autoResize);
771 }
772 
773 #if CR_ENABLE_PAGE_IMAGE_CACHE==1
774 /// cache page image (render in background if necessary)
cachePageImage(int delta)775 void LVDocView::cachePageImage( int delta )
776 {
777 	int offset = -1;
778 	int p = -1;
779 	if ( isPageMode() ) {
780 		p = _page;
781 		if ( delta<0 )
782 		p--;
783 		else if ( delta>0 )
784 		p++;
785 		if ( p<0 || p>=m_pages.length() )
786 		return;
787 	} else {
788 		offset = _pos;
789 		if ( delta<0 )
790 		offset = getPrevPageOffset();
791 		else if ( delta>0 )
792 		offset = getNextPageOffset();
793 	}
794 	//CRLog::trace("cachePageImage: request to cache page [%d] (delta=%d)", offset, delta);
795 	if ( m_imageCache.has(offset, p) ) {
796 		//CRLog::trace("cachePageImage: Page [%d] is found in cache", offset);
797 		return;
798 	}
799 	//CRLog::trace("cachePageImage: starting new render task for page [%d]", offset);
800 	LVDrawBuf * buf = NULL;
801 	if ( m_bitsPerPixel==-1 ) {
802 #if (COLOR_BACKBUFFER==1)
803         buf = new LVColorDrawBuf( m_dx, m_dy, DEF_COLOR_BUFFER_BPP );
804 #else
805 		buf = new LVGrayDrawBuf( m_dx, m_dy, m_drawBufferBits );
806 #endif
807 	} else {
808         if ( m_bitsPerPixel==32 || m_bitsPerPixel==16 ) {
809             buf = new LVColorDrawBuf( m_dx, m_dy, m_bitsPerPixel );
810 		} else {
811 			buf = new LVGrayDrawBuf( m_dx, m_dy, m_bitsPerPixel );
812 		}
813 	}
814 	LVRef<LVDrawBuf> drawbuf( buf );
815 	LVRef<LVThread> thread( new LVDrawThread( this, offset, p, drawbuf ) );
816 	m_imageCache.set( offset, p, drawbuf, thread );
817 	//CRLog::trace("cachePageImage: caching page [%d] is finished", offset);
818 }
819 #endif
820 
exportWolFile(const char * fname,bool flgGray,int levels)821 bool LVDocView::exportWolFile(const char * fname, bool flgGray, int levels) {
822 	LVStreamRef stream = LVOpenFileStream(fname, LVOM_WRITE);
823 	if (!stream)
824 		return false;
825 	return exportWolFile(stream.get(), flgGray, levels);
826 }
827 
exportWolFile(const lChar32 * fname,bool flgGray,int levels)828 bool LVDocView::exportWolFile(const lChar32 * fname, bool flgGray, int levels) {
829 	LVStreamRef stream = LVOpenFileStream(fname, LVOM_WRITE);
830 	if (!stream)
831 		return false;
832 	return exportWolFile(stream.get(), flgGray, levels);
833 }
834 
dumpSection(ldomNode * elem)835 void dumpSection(ldomNode * elem) {
836 	lvRect rc;
837 	elem->getAbsRect(rc);
838 	//fprintf( log.f, "rect(%d, %d, %d, %d)  ", rc.left, rc.top, rc.right, rc.bottom );
839 }
840 
getToc()841 LVTocItem * LVDocView::getToc() {
842 	if (!m_doc)
843 		return NULL;
844         // When just loaded from cache, TocItems are missing their _position
845         // properties (a XPointer object), but all other properties (_path,
846         // _page, _percent) are valid and enough to display TOC.
847         // Avoid calling updatePageNumbers() in that case (as it is expensive
848         // and would delay book opening when loaded from cache - it will be
849         // called when it is really needed: after next full rendering)
850         if (!m_doc->isTocFromCacheValid() || !m_doc->getToc()->hasValidPageNumbers())
851             updatePageNumbers(m_doc->getToc());
852 	return m_doc->getToc();
853 }
854 
getSectionHeader(ldomNode * section)855 static lString32 getSectionHeader(ldomNode * section) {
856 	lString32 header;
857 	if (!section || section->getChildCount() == 0)
858 		return header;
859     ldomNode * child = section->getChildElementNode(0, U"title");
860     if (!child)
861 		return header;
862 	header = child->getText(U' ', 1024);
863 	return header;
864 }
865 
getSectionPage(ldomNode * section,LVRendPageList & pages)866 int getSectionPage(ldomNode * section, LVRendPageList & pages) {
867 	if (!section)
868 		return -1;
869 #if 1
870 	int y = ldomXPointer(section, 0).toPoint().y;
871 #else
872 	lvRect rc;
873 	section->getAbsRect(rc);
874 	int y = rc.top;
875 #endif
876 	int page = -1;
877 	if (y >= 0) {
878 		page = pages.FindNearestPage(y, -1);
879 		//dumpSection( section );
880 		//fprintf(log.f, "page %d: %d->%d..%d\n", page+1, y, pages[page].start, pages[page].start+pages[page].height );
881 	}
882 	return page;
883 }
884 
885 /*
886  static void addTocItems( ldomNode * basesection, LVTocItem * parent )
887  {
888  if ( !basesection || !parent )
889  return;
890  lString32 name = getSectionHeader( basesection );
891  if ( name.empty() )
892  return; // section without header
893  ldomXPointer ptr( basesection, 0 );
894  LVTocItem * item = parent->addChild( name, ptr );
895  int cnt = basesection->getChildCount();
896  for ( int i=0; i<cnt; i++ ) {
897  ldomNode * section = basesection->getChildNode(i);
898  if ( section->getNodeId() != el_section  )
899  continue;
900  addTocItems( section, item );
901  }
902  }
903 
904  void LVDocView::makeToc()
905  {
906  LVTocItem * toc = m_doc->getToc();
907  if ( toc->getChildCount() ) {
908  return;
909  }
910  CRLog::info("LVDocView::makeToc()");
911  toc->clear();
912  ldomNode * body = m_doc->getRootNode();
913  if ( !body )
914  return;
915  body = body->findChildElement( LXML_NS_ANY, el_FictionBook, -1 );
916  if ( body )
917  body = body->findChildElement( LXML_NS_ANY, el_body, 0 );
918  if ( !body )
919  return;
920  int cnt = body->getChildCount();
921  for ( int i=0; i<cnt; i++ ) {
922  ldomNode * section = body->getChildNode(i);
923  if ( section->getNodeId()!=el_section )
924  continue;
925  addTocItems( section, toc );
926  }
927  }
928  */
929 
930 /// update page numbers for items
updatePageNumbers(LVTocItem * item)931 void LVDocView::updatePageNumbers(LVTocItem * item) {
932 	if (!item->getXPointer().isNull()) {
933 		lvPoint p = item->getXPointer().toPoint();
934 		int y = p.y;
935 		int h = GetFullHeight();
936 		int page = getBookmarkPage(item->_position);
937 		if (page >= 0 && page < getPageCount())
938 			item->_page = page;
939 		else
940 			item->_page = -1;
941 		if (y >= 0 && y < h && h > 0)
942 			item->_percent = (int) ((lInt64) y * 10000 / h); // % * 100
943 		else
944 			item->_percent = -1;
945 	} else {
946 		//CRLog::error("Page position is not found for path %s", LCSTR(item->getPath()) );
947 		// unknown position
948                 // Don't update _page of root toc item, as it carries the alternative TOC flag
949                 if (item->_level > 0)
950                     item->_page = -1;
951 		item->_percent = -1;
952 	}
953 	for (int i = 0; i < item->getChildCount(); i++) {
954 		updatePageNumbers(item->getChild(i));
955 	}
956 }
957 
getPageMap()958 LVPageMap * LVDocView::getPageMap() {
959     if (!m_doc)
960         return NULL;
961     if ( !m_doc->getPageMap()->hasValidPageInfo() ) {
962         updatePageMapInfo(m_doc->getPageMap());
963     }
964     return m_doc->getPageMap();
965 }
966 
967 /// update page info for LVPageMapItems
updatePageMapInfo(LVPageMap * pagemap)968 void LVDocView::updatePageMapInfo(LVPageMap * pagemap) {
969     // Ensure page and doc_y never go backward
970     int prev_page = 0;
971     int prev_doc_y = 0;
972     for (int i = 0; i < pagemap->getChildCount(); i++) {
973         LVPageMapItem * item = pagemap->getChild(i);
974         if (!item->getXPointer().isNull()) {
975             int doc_y = item->getDocY(true); // refresh
976             int page = -1;
977             if (doc_y >= 0) {
978                 page = m_pages.FindNearestPage(doc_y, 0);
979                 if (page < 0 && page >= getPageCount())
980                     page = -1;
981             }
982             item->_page = page;
983             if ( item->_page < prev_page )
984                 item->_page = prev_page;
985             else
986                 prev_page = item->_page;
987             if ( item->_doc_y < prev_doc_y )
988                 item->_doc_y = prev_doc_y;
989             else
990                 prev_doc_y = item->_doc_y;
991         }
992         else {
993             item->_page = prev_page;
994             item->_doc_y = prev_doc_y;
995         }
996     }
997     pagemap->_page_info_valid = true;
998 }
999 
1000 
1001 /// get a stream for reading to document internal file (path inside the ZIP for EPUBs,
1002 /// path relative to document directory for non-container documents like HTML)
getDocumentFileStream(lString32 filePath)1003 LVStreamRef LVDocView::getDocumentFileStream( lString32 filePath ) {
1004     if ( !filePath.empty() ) {
1005         LVContainerRef cont = m_doc->getContainer();
1006         if ( cont.isNull() ) // no real container
1007             cont = m_container; // consider document directory as the container
1008         LVStreamRef stream = cont->OpenStream(filePath.c_str(), LVOM_READ);
1009         // if failure, a NULL stream is returned
1010         return stream;
1011     }
1012     return LVStreamRef(); // not found: return NULL ref
1013 }
1014 
1015 /// returns cover page image stream, if any
getCoverPageImageStream()1016 LVStreamRef LVDocView::getCoverPageImageStream() {
1017     lString32 fileName;
1018     //m_doc_props
1019 //    for ( int i=0; i<m_doc_props->getCount(); i++ ) {
1020 //        CRLog::debug("%s = %s", m_doc_props->getName(i), LCSTR(m_doc_props->getValue(i)));
1021 //    }
1022     m_doc_props->getString(DOC_PROP_COVER_FILE, fileName);
1023 //    CRLog::debug("coverpage = %s", LCSTR(fileName));
1024     if ( !fileName.empty() ) {
1025 //        CRLog::debug("trying to open %s", LCSTR(fileName));
1026     	LVContainerRef cont = m_doc->getContainer();
1027     	if ( cont.isNull() )
1028     		cont = m_container;
1029         LVStreamRef stream = cont->OpenStream(fileName.c_str(), LVOM_READ);
1030         if ( stream.isNull() ) {
1031             CRLog::error("Cannot open coverpage image from %s", LCSTR(fileName));
1032             for ( int i=0; i<cont->GetObjectCount(); i++ ) {
1033                 CRLog::info("item %d : %s", i+1, LCSTR(cont->GetObjectInfo(i)->GetName()));
1034             }
1035         } else {
1036 //            CRLog::debug("coverpage file %s is opened ok", LCSTR(fileName));
1037         }
1038         return stream;
1039     }
1040 
1041     // FB2 coverpage
1042 	//CRLog::trace("LVDocView::getCoverPageImage()");
1043 	//m_doc->dumpStatistics();
1044 	lUInt16 path[] = { el_FictionBook, el_description, el_title_info, el_coverpage, 0 };
1045 	//lUInt16 path[] = { el_FictionBook, el_description, el_title_info, el_coverpage, el_image, 0 };
1046 	ldomNode * rootNode = m_doc->getRootNode();
1047 	ldomNode * cover_el = 0;
1048 	if (rootNode) {
1049 		cover_el = rootNode->findChildElement(path);
1050 		if (!cover_el) { // might otherwise be found inside <src-title-info>
1051 			lUInt16 path2[] = { el_FictionBook, el_description, el_src_title_info, el_coverpage, 0 };
1052 			cover_el = rootNode->findChildElement(path2);
1053 		}
1054 	}
1055 	if (cover_el) {
1056 		ldomNode * cover_img_el = cover_el->findChildElement(LXML_NS_ANY,
1057 				el_image, 0);
1058 		if (cover_img_el) {
1059 			LVStreamRef imgsrc = cover_img_el->getObjectImageStream();
1060 			return imgsrc;
1061 		}
1062 	}
1063 	return LVStreamRef(); // not found: return NULL ref
1064 }
1065 
1066 /// returns cover page image source, if any
getCoverPageImage()1067 LVImageSourceRef LVDocView::getCoverPageImage() {
1068 	//    LVStreamRef stream = getCoverPageImageStream();
1069 	//    if ( !stream.isNull() )
1070 	//        CRLog::trace("Image stream size is %d", (int)stream->GetSize() );
1071 	//CRLog::trace("LVDocView::getCoverPageImage()");
1072 	//m_doc->dumpStatistics();
1073 	lUInt16 path[] = { el_FictionBook, el_description, el_title_info, el_coverpage, 0 };
1074 	//lUInt16 path[] = { el_FictionBook, el_description, el_title_info, el_coverpage, el_image, 0 };
1075 	ldomNode * cover_el = 0;
1076 	ldomNode * rootNode = m_doc->getRootNode();
1077 	if (rootNode) {
1078 		cover_el = rootNode->findChildElement(path);
1079 		if (!cover_el) { // might otherwise be found inside <src-title-info>
1080 			lUInt16 path2[] = { el_FictionBook, el_description, el_src_title_info, el_coverpage, 0 };
1081 			cover_el = rootNode->findChildElement(path2);
1082 		}
1083 	}
1084 	if (cover_el) {
1085 		ldomNode * cover_img_el = cover_el->findChildElement(LXML_NS_ANY,
1086 				el_image, 0);
1087 		if (cover_img_el) {
1088 			LVImageSourceRef imgsrc = cover_img_el->getObjectImageSource();
1089 			return imgsrc;
1090 		}
1091 	}
1092 	return LVImageSourceRef(); // not found: return NULL ref
1093 }
1094 
1095 /// draws coverpage to image buffer
drawCoverTo(LVDrawBuf * drawBuf,lvRect & rc)1096 void LVDocView::drawCoverTo(LVDrawBuf * drawBuf, lvRect & rc) {
1097     CRLog::trace("drawCoverTo");
1098 	if (rc.width() < 130 || rc.height() < 130)
1099 		return;
1100 	int base_font_size = 16;
1101 	int w = rc.width();
1102 	if (w < 200)
1103 		base_font_size = 16;
1104 	else if (w < 300)
1105 		base_font_size = 18;
1106 	else if (w < 500)
1107 		base_font_size = 20;
1108 	else if (w < 700)
1109 		base_font_size = 22;
1110 	else
1111 		base_font_size = 24;
1112 	//CRLog::trace("drawCoverTo() - loading fonts...");
1113 	LVFontRef author_fnt(fontMan->GetFont(base_font_size, 700, false,
1114             css_ff_serif, cs8("Times New Roman")));
1115 	LVFontRef title_fnt(fontMan->GetFont(base_font_size + 4, 700, false,
1116             css_ff_serif, cs8("Times New Roman")));
1117 	LVFontRef series_fnt(fontMan->GetFont(base_font_size - 3, 400, true,
1118             css_ff_serif, cs8("Times New Roman")));
1119 	lString32 authors = getAuthors();
1120 	lString32 title = getTitle();
1121 	lString32 series = getSeries();
1122 	if (title.empty())
1123         title = "no title";
1124 	LFormattedText txform;
1125 	if (!authors.empty())
1126 		txform.AddSourceLine(authors.c_str(), authors.length(), 0xFFFFFFFF,
1127 				0xFFFFFFFF, author_fnt.get(), NULL, LTEXT_ALIGN_CENTER,
1128 				author_fnt->getHeight() * 18 / 16);
1129 	txform.AddSourceLine(title.c_str(), title.length(), 0xFFFFFFFF, 0xFFFFFFFF,
1130 			title_fnt.get(), NULL, LTEXT_ALIGN_CENTER,
1131 			title_fnt->getHeight() * 18 / 16);
1132 	if (!series.empty())
1133 		txform.AddSourceLine(series.c_str(), series.length(), 0xFFFFFFFF,
1134 				0xFFFFFFFF, series_fnt.get(), NULL, LTEXT_ALIGN_CENTER,
1135 				series_fnt->getHeight() * 18 / 16);
1136 	int title_w = rc.width() - rc.width() / 4;
1137 	int h = txform.Format((lUInt16)title_w, (lUInt16)rc.height());
1138 
1139 	lvRect imgrc = rc;
1140 
1141 	//CRLog::trace("drawCoverTo() - getting cover image");
1142 	LVImageSourceRef imgsrc = getCoverPageImage();
1143 	LVImageSourceRef defcover = getDefaultCover();
1144 	if (!imgsrc.isNull() && imgrc.height() > 30) {
1145 #ifdef NO_TEXT_IN_COVERPAGE
1146 		h = 0;
1147 #endif
1148 		if (h)
1149 			imgrc.bottom -= h + 16;
1150 		//fprintf( stderr, "Writing coverpage image...\n" );
1151 		int src_dx = imgsrc->GetWidth();
1152 		int src_dy = imgsrc->GetHeight();
1153 		int scale_x = imgrc.width() * 0x10000 / src_dx;
1154 		int scale_y = imgrc.height() * 0x10000 / src_dy;
1155 		if (scale_x < scale_y)
1156 			scale_y = scale_x;
1157 		else
1158 			scale_x = scale_y;
1159 		int dst_dx = (src_dx * scale_x) >> 16;
1160 		int dst_dy = (src_dy * scale_y) >> 16;
1161         if (dst_dx > rc.width() * 6 / 8)
1162 			dst_dx = imgrc.width();
1163         if (dst_dy > rc.height() * 6 / 8)
1164 			dst_dy = imgrc.height();
1165 		//CRLog::trace("drawCoverTo() - drawing image");
1166         // It's best to use a 16bpp LVColorDrawBuf as the intermediate buffer,
1167         // as using 32bpp would mess colors up when drawBuf is itself 32bpp.
1168         LVColorDrawBuf buf2(src_dx, src_dy, 16);
1169         buf2.Draw(imgsrc, 0, 0, src_dx, src_dy, true);
1170         drawBuf->DrawRescaled(&buf2, imgrc.left + (imgrc.width() - dst_dx) / 2,
1171                 imgrc.top + (imgrc.height() - dst_dy) / 2, dst_dx, dst_dy, 0);
1172 	} else if (!defcover.isNull()) {
1173 		if (h)
1174 			imgrc.bottom -= h + 16;
1175 		// draw default cover with title at center
1176 		imgrc = rc;
1177 		int src_dx = defcover->GetWidth();
1178 		int src_dy = defcover->GetHeight();
1179 		int scale_x = imgrc.width() * 0x10000 / src_dx;
1180 		int scale_y = imgrc.height() * 0x10000 / src_dy;
1181 		if (scale_x < scale_y)
1182 			scale_y = scale_x;
1183 		else
1184 			scale_x = scale_y;
1185 		int dst_dx = (src_dx * scale_x) >> 16;
1186 		int dst_dy = (src_dy * scale_y) >> 16;
1187 		if (dst_dx > rc.width() - 10)
1188 			dst_dx = imgrc.width();
1189 		if (dst_dy > rc.height() - 10)
1190 			dst_dy = imgrc.height();
1191 		//CRLog::trace("drawCoverTo() - drawing image");
1192 		drawBuf->Draw(defcover, imgrc.left + (imgrc.width() - dst_dx) / 2,
1193 				imgrc.top + (imgrc.height() - dst_dy) / 2, dst_dx, dst_dy);
1194 		//CRLog::trace("drawCoverTo() - drawing text");
1195 		txform.Draw(drawBuf, (rc.right + rc.left - title_w) / 2, (rc.bottom
1196 				+ rc.top - h) / 2, NULL);
1197 		//CRLog::trace("drawCoverTo() - done");
1198 		return;
1199 	} else {
1200 		imgrc.bottom = imgrc.top;
1201 	}
1202 	rc.top = imgrc.bottom;
1203 	//CRLog::trace("drawCoverTo() - drawing text");
1204 	if (h)
1205 		txform.Draw(drawBuf, (rc.right + rc.left - title_w) / 2, (rc.bottom
1206 				+ rc.top - h) / 2, NULL);
1207 	//CRLog::trace("drawCoverTo() - done");
1208 }
1209 
1210 /// export to WOL format
exportWolFile(LVStream * stream,bool flgGray,int levels)1211 bool LVDocView::exportWolFile(LVStream * stream, bool flgGray, int levels) {
1212 	checkRender();
1213 	int save_m_dx = m_dx;
1214 	int save_m_dy = m_dy;
1215 	int old_flags = m_pageHeaderInfo;
1216 	int save_pos = _pos;
1217 	int save_page = _pos;
1218 	bool showCover = getShowCover();
1219 	m_pageHeaderInfo &= ~(PGHDR_CLOCK | PGHDR_BATTERY);
1220 	int dx = 600; // - m_pageMargins.left - m_pageMargins.right;
1221 	int dy = 800; // - m_pageMargins.top - m_pageMargins.bottom;
1222 	Resize(dx, dy);
1223 
1224 	LVRendPageList &pages = m_pages;
1225 
1226 	//Render(dx, dy, &pages);
1227 
1228 	const lChar8 * * table = GetCharsetUnicode2ByteTable(U"windows-1251");
1229 
1230 	//ldomXPointer bm = getBookmark();
1231 	{
1232 		WOLWriter wol(stream);
1233 		lString8 authors = UnicodeTo8Bit(getAuthors(), table);
1234 		lString8 name = UnicodeTo8Bit(getTitle(), table);
1235         wol.addTitle(name, cs8("-"), authors, cs8("-"), //adapter
1236                 cs8("-"), //translator
1237                 cs8("-"), //publisher
1238                 cs8("-"), //2006-11-01
1239                 cs8("-"), //This is introduction.
1240                 cs8("") //ISBN
1241 		);
1242 
1243 		LVGrayDrawBuf cover(600, 800);
1244 		lvRect coverRc(0, 0, 600, 800);
1245 		cover.Clear(m_backgroundColor);
1246 		drawCoverTo(&cover, coverRc);
1247 		wol.addCoverImage(cover);
1248 
1249 		int lastPercent = 0;
1250 		for (int i = showCover ? 1 : 0; i < pages.length(); i
1251 				+= getVisiblePageCount()) {
1252 			int percent = i * 100 / pages.length();
1253 			percent -= percent % 5;
1254 			if (percent != lastPercent) {
1255 				lastPercent = percent;
1256 				if (m_callback != NULL)
1257 					m_callback->OnExportProgress(percent);
1258 			}
1259 			LVGrayDrawBuf drawbuf(600, 800, flgGray ? 2 : 1); //flgGray ? 2 : 1);
1260 			//drawbuf.SetBackgroundColor(0xFFFFFF);
1261 			//drawbuf.SetTextColor(0x000000);
1262 			drawbuf.Clear(m_backgroundColor);
1263 			drawPageTo(&drawbuf, *pages[i], NULL, pages.length(), 0);
1264 			_pos = pages[i]->start;
1265 			_page = i;
1266 			Draw(drawbuf, -1, _page, true);
1267 			if (!flgGray) {
1268 				drawbuf.ConvertToBitmap(false);
1269 				drawbuf.Invert();
1270 			} else {
1271 				//drawbuf.Invert();
1272 			}
1273 			wol.addImage(drawbuf);
1274 		}
1275 
1276 		// add TOC
1277 		ldomNode * body = m_doc->nodeFromXPath(lString32(
1278                 "/FictionBook/body[1]"));
1279 		lUInt16 section_id = m_doc->getElementNameIndex(U"section");
1280 
1281 		if (body) {
1282 			int l1n = 0;
1283 			for (int l1 = 0; l1 < 1000; l1++) {
1284 				ldomNode * l1section = body->findChildElement(LXML_NS_ANY,
1285 						section_id, l1);
1286 				if (!l1section)
1287 					break;
1288 				lString8 title = UnicodeTo8Bit(getSectionHeader(l1section),
1289 						table);
1290 				int page = getSectionPage(l1section, pages);
1291 				if (!showCover)
1292 					page++;
1293 				if (!title.empty() && page >= 0) {
1294 					wol.addTocItem(++l1n, 0, 0, page, title);
1295 					int l2n = 0;
1296 					if (levels < 2)
1297 						continue;
1298 					for (int l2 = 0; l2 < 1000; l2++) {
1299 						ldomNode * l2section = l1section->findChildElement(
1300 								LXML_NS_ANY, section_id, l2);
1301 						if (!l2section)
1302 							break;
1303 						lString8 title = UnicodeTo8Bit(getSectionHeader(
1304 								l2section), table);
1305 						int page = getSectionPage(l2section, pages);
1306 						if (!title.empty() && page >= 0) {
1307 							wol.addTocItem(l1n, ++l2n, 0, page, title);
1308 							int l3n = 0;
1309 							if (levels < 3)
1310 								continue;
1311 							for (int l3 = 0; l3 < 1000; l3++) {
1312 								ldomNode * l3section =
1313 										l2section->findChildElement(
1314 												LXML_NS_ANY, section_id, l3);
1315 								if (!l3section)
1316 									break;
1317 								lString8 title = UnicodeTo8Bit(
1318 										getSectionHeader(l3section), table);
1319 								int page = getSectionPage(l3section, pages);
1320 								if (!title.empty() && page >= 0) {
1321 									wol.addTocItem(l1n, l2n, ++l3n, page, title);
1322 								}
1323 							}
1324 						}
1325 					}
1326 				}
1327 			}
1328 		}
1329 	}
1330 	m_pageHeaderInfo = old_flags;
1331 	_pos = save_pos;
1332 	_page = save_page;
1333 	bool rotated =
1334 #if CR_INTERNAL_PAGE_ORIENTATION==1
1335 			(GetRotateAngle()&1);
1336 #else
1337 			false;
1338 #endif
1339 	int ndx = rotated ? save_m_dy : save_m_dx;
1340 	int ndy = rotated ? save_m_dx : save_m_dy;
1341 	Resize(ndx, ndy);
1342 	clearImageCache();
1343 
1344 	return true;
1345 }
1346 
GetFullHeight()1347 int LVDocView::GetFullHeight() {
1348 	LVLock lock(getMutex());
1349     CHECK_RENDER("getFullHeight()");
1350 	RenderRectAccessor rd(m_doc->getRootNode());
1351 	return (rd.getHeight() + rd.getY());
1352 }
1353 
1354 /// calculate page header height
getPageHeaderHeight()1355 int LVDocView::getPageHeaderHeight() {
1356 	if (getPageheaderPosition() == 0)
1357 		return 0;
1358 	if (!getPageHeaderInfo())
1359 		return 0;
1360 	if (!getInfoFont())
1361 		return 0;
1362 	int h = getInfoFont()->getHeight();
1363 	int bh = m_batteryIcons.length()>0 ? m_batteryIcons[0]->GetHeight() * 11/10 + HEADER_MARGIN / 2 : 0;
1364 	if ( bh>h )
1365 		h = bh;
1366 	return h + HEADER_MARGIN;
1367 }
1368 
1369 /// calculate page header rectangle
getPageHeaderRectangle(int pageIndex,lvRect & headerRc)1370 void LVDocView::getPageHeaderRectangle(int pageIndex, lvRect & headerRc) {
1371 	lvRect pageRc;
1372 	getPageRectangle(pageIndex, pageRc);
1373 	headerRc = pageRc;
1374 	if (pageIndex == 0 && m_showCover) {
1375 		headerRc.bottom = 0;
1376 	} else {
1377 		int h = getPageHeaderHeight();
1378 		int propHeaderMargin = m_props->getIntDef(PROP_ROUNDED_CORNERS_MARGIN, 0);
1379 		switch (m_pageHeaderPos) {
1380 			case PAGE_HEADER_POS_TOP:
1381 				// header/status at page header
1382 				headerRc.bottom = headerRc.top + h;
1383 				headerRc.top += HEADER_MARGIN;
1384 				break;
1385 			case PAGE_HEADER_POS_BOTTOM:
1386 				// header/status at page footer
1387 				headerRc.bottom -= HEADER_MARGIN;
1388 				headerRc.top = headerRc.bottom - h;
1389 				break;
1390 		}
1391 		headerRc.left += HEADER_MARGIN + propHeaderMargin;
1392 		headerRc.right -= HEADER_MARGIN + propHeaderMargin;
1393 	}
1394 }
1395 
1396 /// returns current time representation string
getTimeString()1397 lString32 LVDocView::getTimeString() {
1398 	time_t t = (time_t) time(0);
1399 	tm * bt = localtime(&t);
1400 	char str[12];
1401 	sprintf(str, "%02d:%02d", bt->tm_hour, bt->tm_min);
1402 	return Utf8ToUnicode(lString8(str));
1403 }
1404 
1405 /// draw battery state to buffer
drawBatteryState(LVDrawBuf * drawbuf,const lvRect & batteryRc,bool)1406 void LVDocView::drawBatteryState(LVDrawBuf * drawbuf, const lvRect & batteryRc,
1407 		bool /*isVertical*/) {
1408 	if (m_battery_state == CR_BATTERY_STATE_NO_BATTERY)
1409 		return;
1410 	LVDrawStateSaver saver(*drawbuf);
1411 	int textColor = drawbuf->GetBackgroundColor();
1412 	int bgColor = drawbuf->GetTextColor();
1413 	drawbuf->SetTextColor(bgColor);
1414 	drawbuf->SetBackgroundColor(textColor);
1415 	LVRefVec<LVImageSource> icons;
1416 	bool drawPercent = m_props->getBoolDef(PROP_SHOW_BATTERY_PERCENT, true) || m_batteryIcons.size()<=2;
1417 	if ( m_batteryIcons.size()>1 ) {
1418 		icons.add(m_batteryIcons[0]);
1419 		if ( drawPercent ) {
1420             m_batteryFont = fontMan->GetFont(m_batteryIcons[0]->GetHeight()-1, 900, false,
1421                     DEFAULT_FONT_FAMILY, m_statusFontFace);
1422             icons.add(m_batteryIcons[m_batteryIcons.length()-1]);
1423 		} else {
1424 			for ( int i=1; i<m_batteryIcons.length()-1; i++ )
1425 				icons.add(m_batteryIcons[i]);
1426 		}
1427 	} else {
1428 		if ( m_batteryIcons.size()==1 )
1429 			icons.add(m_batteryIcons[0]);
1430 	}
1431 	LVDrawBatteryIcon(drawbuf, batteryRc, m_battery_state, m_battery_state
1432 			== CR_BATTERY_STATE_CHARGING, icons, drawPercent ? m_batteryFont.get() : NULL);
1433 #if 0
1434 	if ( m_batteryIcons.length()>1 ) {
1435 		int iconIndex = ((m_batteryIcons.length() - 1 ) * m_battery_state + (100/m_batteryIcons.length()/2) )/ 100;
1436 		if ( iconIndex<0 )
1437 		iconIndex = 0;
1438 		if ( iconIndex>m_batteryIcons.length()-1 )
1439 		iconIndex = m_batteryIcons.length()-1;
1440 		CRLog::trace("battery icon index = %d", iconIndex);
1441 		LVImageSourceRef icon = m_batteryIcons[iconIndex];
1442 		drawbuf->Draw( icon, (batteryRc.left + batteryRc.right - icon->GetWidth() ) / 2,
1443 				(batteryRc.top + batteryRc.bottom - icon->GetHeight())/2,
1444 				icon->GetWidth(),
1445 				icon->GetHeight() );
1446 	} else {
1447 		lvRect rc = batteryRc;
1448 		if ( m_battery_state<0 )
1449 		return;
1450 		lUInt32 cl = 0xA0A0A0;
1451 		lUInt32 cl2 = 0xD0D0D0;
1452 		if ( drawbuf->GetBitsPerPixel()<=2 ) {
1453 			cl = 1;
1454 			cl2 = 2;
1455 		}
1456 #if 1
1457 
1458 		if ( isVertical ) {
1459 			int h = rc.height();
1460 			h = ( (h - 4) / 4 * 4 ) + 3;
1461 			int dh = (rc.height() - h) / 2;
1462 			rc.bottom -= dh;
1463 			rc.top = rc.bottom - h;
1464 			int w = rc.width();
1465 			int h0 = 4; //h / 6;
1466 			int w0 = w / 3;
1467 			// contour
1468 			drawbuf->FillRect( rc.left, rc.top+h0, rc.left+1, rc.bottom, cl );
1469 			drawbuf->FillRect( rc.right-1, rc.top+h0, rc.right, rc.bottom, cl );
1470 
1471 			drawbuf->FillRect( rc.left, rc.top+h0, rc.left+w0, rc.top+h0+1, cl );
1472 			drawbuf->FillRect( rc.right-w0, rc.top+h0, rc.right, rc.top+h0+1, cl );
1473 
1474 			drawbuf->FillRect( rc.left+w0-1, rc.top, rc.left+w0, rc.top+h0, cl );
1475 			drawbuf->FillRect( rc.right-w0, rc.top, rc.right-w0+1, rc.top+h0, cl );
1476 
1477 			drawbuf->FillRect( rc.left+w0, rc.top, rc.right-w0, rc.top+1, cl );
1478 			drawbuf->FillRect( rc.left, rc.bottom-1, rc.right, rc.bottom, cl );
1479 			// fill
1480 			int miny = rc.bottom - 2 - (h - 4) * m_battery_state / 100;
1481 			for ( int i=0; i<h-4; i++ ) {
1482 				if ( (i&3) != 3 ) {
1483 					int y = rc.bottom - 2 - i;
1484 					int w = 2;
1485 					if ( y < rc.top + h0 + 2 )
1486 					w = w0 + 1;
1487 					lUInt32 c = cl2;
1488 					if ( y >= miny )
1489 					c = cl;
1490 					drawbuf->FillRect( rc.left+w, y-1, rc.right-w, y, c );
1491 				}
1492 			}
1493 		} else {
1494 			// horizontal
1495 			int h = rc.width();
1496 			h = ( (h - 4) / 4 * 4 ) + 3;
1497 			int dh = (rc.height() - h) / 2;
1498 			rc.right -= dh;
1499 			rc.left = rc.right - h;
1500 			h = rc.height();
1501 			dh = h - (rc.width() * 4/8 + 1);
1502 			if ( dh>0 ) {
1503 				rc.bottom -= dh/2;
1504 				rc.top += (dh/2);
1505 				h = rc.height();
1506 			}
1507 			int w = rc.width();
1508 			int h0 = h / 3; //h / 6;
1509 			int w0 = 4;
1510 			// contour
1511 			drawbuf->FillRect( rc.left+w0, rc.top, rc.right, rc.top+1, cl );
1512 			drawbuf->FillRect( rc.left+w0, rc.bottom-1, rc.right, rc.bottom, cl );
1513 
1514 			drawbuf->FillRect( rc.left+w0, rc.top, rc.left+w0+1, rc.top+h0, cl );
1515 			drawbuf->FillRect( rc.left+w0, rc.bottom-h0, rc.left+w0+1, rc.bottom, cl );
1516 
1517 			drawbuf->FillRect( rc.left, rc.top+h0-1, rc.left+w0, rc.top+h0, cl );
1518 			drawbuf->FillRect( rc.left, rc.bottom-h0, rc.left+w0, rc.bottom-h0+1, cl );
1519 
1520 			drawbuf->FillRect( rc.left, rc.top+h0, rc.left+1, rc.bottom-h0, cl );
1521 			drawbuf->FillRect( rc.right-1, rc.top, rc.right, rc.bottom, cl );
1522 			// fill
1523 			int minx = rc.right - 2 - (w - 4) * m_battery_state / 100;
1524 			for ( int i=0; i<w-4; i++ ) {
1525 				if ( (i&3) != 3 ) {
1526 					int x = rc.right - 2 - i;
1527 					int h = 2;
1528 					if ( x < rc.left + w0 + 2 )
1529 					h = h0 + 1;
1530 					lUInt32 c = cl2;
1531 					if ( x >= minx )
1532 					c = cl;
1533 					drawbuf->FillRect( x-1, rc.top+h, x, rc.bottom-h, c );
1534 				}
1535 			}
1536 		}
1537 #else
1538 		//lUInt32 cl = getTextColor();
1539 		int h = rc.height() / 6;
1540 		if ( h<5 )
1541 		h = 5;
1542 		int n = rc.height() / h;
1543 		int dy = rc.height() % h / 2;
1544 		if ( n<1 )
1545 		n = 1;
1546 		int k = m_battery_state * n / 100;
1547 		for ( int i=0; i<n; i++ ) {
1548 			lvRect rrc = rc;
1549 			rrc.bottom -= h * i + dy;
1550 			rrc.top = rrc.bottom - h + 1;
1551 			int dx = (i<n-1) ? 0 : rc.width()/5;
1552 			rrc.left += dx;
1553 			rrc.right -= dx;
1554 			if ( i<k ) {
1555 				// full
1556 				drawbuf->FillRect( rrc.left, rrc.top, rrc.right, rrc.bottom, cl );
1557 			} else {
1558 				// empty
1559 				drawbuf->FillRect( rrc.left, rrc.top, rrc.right, rrc.top+1, cl );
1560 				drawbuf->FillRect( rrc.left, rrc.bottom-1, rrc.right, rrc.bottom, cl );
1561 				drawbuf->FillRect( rrc.left, rrc.top, rrc.left+1, rrc.bottom, cl );
1562 				drawbuf->FillRect( rrc.right-1, rrc.top, rrc.right, rrc.bottom, cl );
1563 			}
1564 		}
1565 #endif
1566 	}
1567 #endif
1568 }
1569 
1570 /// returns section bounds, in 1/100 of percent
getSectionBounds(bool for_external_update)1571 LVArray<int> & LVDocView::getSectionBounds( bool for_external_update ) {
1572 	if (for_external_update || m_section_bounds_externally_updated) {
1573 		// Progress bar markes will be externally updated: we don't care
1574 		// about m_section_bounds_valid and we never trash it here.
1575 		// It's the frontend responsability to notice it needs some
1576 		// update and to update it.
1577 		m_section_bounds_externally_updated = true;
1578 		return m_section_bounds;
1579 	}
1580 	if (m_section_bounds_valid)
1581 		return m_section_bounds;
1582 	m_section_bounds.clear();
1583 	m_section_bounds.add(0);
1584     // Get sections from FB2 books
1585     ldomNode * body = m_doc->nodeFromXPath(cs32("/FictionBook/body[1]"));
1586 	lUInt16 section_id = m_doc->getElementNameIndex(U"section");
1587     if (body == NULL) {
1588         // Get sections from EPUB books
1589         body = m_doc->nodeFromXPath(cs32("/body[1]"));
1590         section_id = m_doc->getElementNameIndex(U"DocFragment");
1591     }
1592 	int fh = GetFullHeight();
1593     int pc = getVisiblePageCount();
1594 	if (body && fh > 0) {
1595 		int cnt = body->getChildCount();
1596 		for (int i = 0; i < cnt; i++) {
1597 
1598             ldomNode * l1section = body->getChildElementNode(i, section_id);
1599             if (!l1section)
1600 				continue;
1601 
1602             lvRect rc;
1603             l1section->getAbsRect(rc);
1604             if (getViewMode() == DVM_SCROLL) {
1605                 int p = (int) (((lInt64) rc.top * 10000) / fh);
1606                 m_section_bounds.add(p);
1607             } else {
1608                 int fh = m_pages.length();
1609                 if ( (pc==2 && (fh&1)) )
1610                     fh++;
1611                 int p = m_pages.FindNearestPage(rc.top, 0);
1612                 if (fh > 1)
1613                     m_section_bounds.add((int) (((lInt64) p * 10000) / fh));
1614             }
1615 
1616 		}
1617 	}
1618 	m_section_bounds.add(10000);
1619 	m_section_bounds_valid = true;
1620 	return m_section_bounds;
1621 }
1622 
getPosEndPagePercent()1623 int LVDocView::getPosEndPagePercent() {
1624     LVLock lock(getMutex());
1625     checkPos();
1626     if (getViewMode() == DVM_SCROLL) {
1627         int fh = GetFullHeight();
1628         int p = GetPos() + m_pageRects[0].height() - m_pageMargins.top - m_pageMargins.bottom - 10;
1629         if (fh > 0)
1630             return (int) (((lInt64) p * 10000) / fh);
1631         else
1632             return 0;
1633     } else {
1634         int pc = m_pages.length();
1635         if (pc > 0) {
1636             int p = getCurPage() + 1;// + 1;
1637             if (getVisiblePageCount() > 1)
1638                 p++;
1639             if (p > pc - 1)
1640                 p = pc - 1;
1641             if (p < 0)
1642                 p = 0;
1643             p = m_pages[p]->start - 10;
1644             int fh = GetFullHeight();
1645             if (fh > 0)
1646                 return (int) (((lInt64) p * 10000) / fh);
1647             else
1648                 return 0;
1649         } else
1650             return 0;
1651     }
1652 }
1653 
getPosPercent()1654 int LVDocView::getPosPercent() {
1655 	LVLock lock(getMutex());
1656 	checkPos();
1657 	if (getViewMode() == DVM_SCROLL) {
1658 		int fh = GetFullHeight();
1659 		int p = GetPos();
1660 		if (fh > 0)
1661 			return (int) (((lInt64) p * 10000) / fh);
1662 		else
1663 			return 0;
1664 	} else {
1665         int fh = m_pages.length();
1666         if ( (getVisiblePageCount()==2 && (fh&1)) )
1667             fh++;
1668         int p = getCurPage();// + 1;
1669 //        if ( getVisiblePageCount()>1 )
1670 //            p++;
1671 		if (fh > 0)
1672 			return (int) (((lInt64) p * 10000) / fh);
1673 		else
1674 			return 0;
1675 	}
1676 }
1677 
getPageRectangle(int pageIndex,lvRect & pageRect)1678 void LVDocView::getPageRectangle(int pageIndex, lvRect & pageRect) {
1679 	if ((pageIndex & 1) == 0 || (getVisiblePageCount() < 2))
1680 		pageRect = m_pageRects[0];
1681 	else
1682 		pageRect = m_pageRects[1];
1683 }
1684 
getNavigationBarRectangle(lvRect & navRect)1685 void LVDocView::getNavigationBarRectangle(lvRect & navRect) {
1686 	getNavigationBarRectangle(getVisiblePageCount() == 2 ? 1 : 2, navRect);
1687 }
1688 
getNavigationBarRectangle(int pageIndex,lvRect & navRect)1689 void LVDocView::getNavigationBarRectangle(int pageIndex, lvRect & navRect) {
1690 	lvRect headerRect;
1691 	getPageHeaderRectangle(pageIndex, headerRect);
1692 	navRect = headerRect;
1693 	if (headerRect.bottom <= headerRect.top)
1694 		return;
1695 	navRect.top = navRect.bottom - 6;
1696 }
1697 
drawNavigationBar(LVDrawBuf * drawbuf,int pageIndex,int percent)1698 void LVDocView::drawNavigationBar(LVDrawBuf * drawbuf, int pageIndex,
1699 		int percent) {
1700     CR_UNUSED2(drawbuf, percent);
1701 	//LVArray<int> & sbounds = getSectionBounds();
1702 	lvRect navBar;
1703 	getNavigationBarRectangle(pageIndex, navBar);
1704 	//bool leftPage = (getVisiblePageCount()==2 && !(pageIndex&1) );
1705 
1706 	//lUInt32 cl1 = 0xA0A0A0;
1707 	//lUInt32 cl2 = getBackgroundColor();
1708 }
1709 
1710 /// sets battery state
setBatteryState(int newState)1711 bool LVDocView::setBatteryState(int newState) {
1712 	if (m_battery_state == newState)
1713 		return false;
1714 	CRLog::info("New battery state: %d", newState);
1715 	m_battery_state = newState;
1716 	clearImageCache();
1717 	return true;
1718 }
1719 
1720 /// set list of battery icons to display battery state
setBatteryIcons(const LVRefVec<LVImageSource> & icons)1721 void LVDocView::setBatteryIcons(const LVRefVec<LVImageSource> & icons) {
1722 	m_batteryIcons = icons;
1723 }
1724 
fitTextWidthWithEllipsis(lString32 text,LVFontRef font,int maxwidth)1725 lString32 fitTextWidthWithEllipsis(lString32 text, LVFontRef font, int maxwidth) {
1726 	int w = font->getTextWidth(text.c_str(), text.length());
1727 	if (w <= maxwidth)
1728 		return text;
1729 	int len;
1730 	for (len = text.length() - 1; len > 1; len--) {
1731         lString32 s = text.substr(0, len) + "...";
1732 		w = font->getTextWidth(s.c_str(), s.length());
1733 		if (w <= maxwidth)
1734 			return s;
1735 	}
1736     return lString32::empty_str;
1737 }
1738 
1739 /// substitute page header with custom text (e.g. to be used while loading)
setPageHeaderOverride(lString32 s)1740 void LVDocView::setPageHeaderOverride(lString32 s) {
1741 	m_pageHeaderOverride = s;
1742 	clearImageCache();
1743 }
1744 
1745 /// draw page header to buffer
drawPageHeader(LVDrawBuf * drawbuf,const lvRect & headerRc,int pageIndex,int phi,int pageCount)1746 void LVDocView::drawPageHeader(LVDrawBuf * drawbuf, const lvRect & headerRc,
1747 		int pageIndex, int phi, int pageCount) {
1748 	lvRect oldcr;
1749 	drawbuf->GetClipRect(&oldcr);
1750 	lvRect hrc = headerRc;
1751     hrc.bottom += 2;
1752 	drawbuf->SetClipRect(&hrc);
1753 	bool drawGauge = true;
1754 	lvRect info = headerRc;
1755 //    if ( m_statusColor!=0xFF000000 ) {
1756 //        CRLog::trace("Status color = %06x, textColor=%06x", m_statusColor, getTextColor());
1757 //    } else {
1758 //        CRLog::trace("Status color = TRANSPARENT, textColor=%06x", getTextColor());
1759 //    }
1760 	lUInt32 cl1 = m_statusColor!=0xFF000000 ? m_statusColor : getTextColor();
1761     //lUInt32 cl2 = getBackgroundColor();
1762     //lUInt32 cl3 = 0xD0D0D0;
1763     //lUInt32 cl4 = 0xC0C0C0;
1764 	drawbuf->SetTextColor(cl1);
1765 	//lUInt32 pal[4];
1766 	int percent = getPosPercent();
1767 	bool leftPage = (getVisiblePageCount() == 2 && !(pageIndex & 1));
1768 	if (leftPage || !drawGauge)
1769 		percent = 10000;
1770         int percent_pos = /*info.left + */percent * info.width() / 10000;
1771 	//    int gh = 3; //drawGauge ? 3 : 1;
1772 	LVArray<int> & sbounds = getSectionBounds();
1773 	lvRect navBar;
1774 	getNavigationBarRectangle(pageIndex, navBar);
1775 	int gpos = 0;
1776 	switch (m_pageHeaderPos) {
1777 		case PAGE_HEADER_POS_TOP:
1778 			gpos = info.bottom;
1779 			break;
1780 		case PAGE_HEADER_POS_BOTTOM:
1781 			gpos = info.top + 4;
1782 			break;
1783 		default:
1784 			break;
1785 	}
1786 //	if (drawbuf->GetBitsPerPixel() <= 2) {
1787 //		// gray
1788 //		cl3 = 1;
1789 //		cl4 = cl1;
1790 //		//pal[0] = cl1;
1791 //	}
1792 	if ( leftPage )
1793 		drawbuf->FillRect(info.left, gpos - 2, info.right, gpos - 2 + 1, cl1);
1794         //drawbuf->FillRect(info.left+percent_pos, gpos-gh, info.right, gpos-gh+1, cl1 ); //cl3
1795         //      drawbuf->FillRect(info.left + percent_pos, gpos - 2, info.right, gpos - 2
1796         //                      + 1, cl1); // cl3
1797 
1798 	int sbound_index = 0;
1799 	bool enableMarks = !leftPage && (phi & PGHDR_CHAPTER_MARKS) && sbounds.length()<info.width()/5;
1800 	int w = GetWidth();
1801 	int h = GetHeight();
1802 	if (w > h)
1803 		w = h;
1804 	int markh = 3;
1805 	int markw = 1;
1806 	if (w > 700) {
1807 		markh = 7;
1808         markw = 2;
1809 	}
1810 	for ( int x = info.left; x<info.right; x++ ) {
1811 		int cl = -1;
1812 		int sz = 1;
1813 		int szx = 1;
1814 		int boundCategory = 0;
1815 		while ( enableMarks && sbound_index<sbounds.length() ) {
1816 			int sx = info.left + sbounds[sbound_index] * (info.width() - 1) / 10000;
1817 			if ( sx<x ) {
1818 				sbound_index++;
1819 				continue;
1820 			}
1821 			if ( sx==x ) {
1822 				boundCategory = 1;
1823 			}
1824 			break;
1825 		}
1826 		if ( leftPage ) {
1827 			cl = cl1;
1828 			sz = 1;
1829 		} else {
1830             if ( x < info.left + percent_pos ) {
1831                 sz = 3;
1832 				if ( boundCategory==0 )
1833 					cl = cl1;
1834                 else
1835                     sz = 0;
1836             } else {
1837 				if ( boundCategory!=0 )
1838 					sz = markh;
1839 				cl = cl1;
1840 				szx = markw;
1841 			}
1842 		}
1843         if ( cl!=-1 && sz>0 )
1844             drawbuf->FillRect(x, gpos - 2 - sz/2, x + szx, gpos - 2 + sz/2 + 1, cl);
1845 	}
1846 
1847 	lString32 text;
1848 	//int iy = info.top; // + (info.height() - m_infoFont->getHeight()) * 2 / 3;
1849 	int iy = info.top + /*m_infoFont->getHeight() +*/ (info.height() - m_infoFont->getHeight()) / 2 - HEADER_MARGIN/2;
1850 	if (PAGE_HEADER_POS_BOTTOM == m_pageHeaderPos)
1851 		iy += 4 + 1;
1852 
1853 	if (!m_pageHeaderOverride.empty()) {
1854 		text = m_pageHeaderOverride;
1855 	} else {
1856 
1857 //		if (!leftPage) {
1858 //			drawbuf->FillRect(info.left, gpos - 3, info.left + percent_pos,
1859 //					gpos - 3 + 1, cl1);
1860 //			drawbuf->FillRect(info.left, gpos - 1, info.left + percent_pos,
1861 //					gpos - 1 + 1, cl1);
1862 //		}
1863 
1864 		// disable section marks for left page, and for too many marks
1865 //		if (!leftPage && (phi & PGHDR_CHAPTER_MARKS) && sbounds.length()<info.width()/5 ) {
1866 //			for (int i = 0; i < sbounds.length(); i++) {
1867 //				int x = info.left + sbounds[i] * (info.width() - 1) / 10000;
1868 //				lUInt32 c = x < info.left + percent_pos ? cl2 : cl1;
1869 //				drawbuf->FillRect(x, gpos - 4, x + 1, gpos - 0 + 2, c);
1870 //			}
1871 //		}
1872 
1873 		if (getVisiblePageCount() == 1 || !(pageIndex & 1)) {
1874 			int dwIcons = 0;
1875 			int icony = iy + m_infoFont->getHeight() / 2;
1876 			for (int ni = 0; ni < m_headerIcons.length(); ni++) {
1877 				LVImageSourceRef icon = m_headerIcons[ni];
1878 				int h = icon->GetHeight();
1879 				int w = icon->GetWidth();
1880 				drawbuf->Draw(icon, info.left + dwIcons, icony - h / 2, w, h);
1881 				dwIcons += w + 4;
1882 			}
1883 			info.left += dwIcons;
1884 		}
1885 
1886 		bool batteryPercentNormalFont = false; // PROP_SHOW_BATTERY_PERCENT
1887 		if ((phi & PGHDR_BATTERY) && m_battery_state >= CR_BATTERY_STATE_CHARGING) {
1888 			batteryPercentNormalFont = m_props->getBoolDef(PROP_SHOW_BATTERY_PERCENT, true) || m_batteryIcons.size()<=2;
1889 			if ( !batteryPercentNormalFont ) {
1890 				lvRect brc = info;
1891 				brc.right -= 2;
1892 				//brc.top += 1;
1893 				//brc.bottom -= 2;
1894 				int h = brc.height();
1895 				int batteryIconWidth = 32;
1896 				if (m_batteryIcons.length() > 0)
1897 					batteryIconWidth = m_batteryIcons[0]->GetWidth();
1898 				bool isVertical = (h > 30);
1899 				//if ( isVertical )
1900 				//    brc.left = brc.right - brc.height()/2;
1901 				//else
1902 				brc.left = brc.right - batteryIconWidth - 2;
1903 				brc.bottom -= 5;
1904 				drawBatteryState(drawbuf, brc, isVertical);
1905 				info.right = brc.left - info.height() / 2;
1906 			}
1907 		}
1908 		lString32 pageinfo;
1909 		if (pageCount > 0) {
1910 			if (phi & PGHDR_PAGE_NUMBER)
1911                 pageinfo += fmt::decimal(pageIndex + 1);
1912             if (phi & PGHDR_PAGE_COUNT) {
1913                 if ( !pageinfo.empty() )
1914                     pageinfo += " / ";
1915                 pageinfo += fmt::decimal(pageCount);
1916             }
1917             if (phi & PGHDR_PERCENT) {
1918                 if ( !pageinfo.empty() )
1919                     pageinfo += "  ";
1920                 //pageinfo += lString32::itoa(percent/100)+U"%"; //+U"."+lString32::itoa(percent/10%10)+U"%";
1921                 pageinfo += fmt::decimal(percent/100);
1922                 pageinfo += ",";
1923                 int pp = percent%100;
1924                 if ( pp<10 )
1925                     pageinfo << "0";
1926                 pageinfo << fmt::decimal(pp) << "%";
1927             }
1928             if ( batteryPercentNormalFont && m_battery_state>=0 ) {
1929                 pageinfo << "  [" << fmt::decimal(m_battery_state) << "%]";
1930             }
1931 		}
1932 		int piw = 0;
1933 		if (!pageinfo.empty()) {
1934 			piw = m_infoFont->getTextWidth(pageinfo.c_str(), pageinfo.length());
1935 			m_infoFont->DrawTextString(drawbuf, info.right - piw, iy,
1936 					pageinfo.c_str(), pageinfo.length(), U' ', NULL, false);
1937 			info.right -= piw + info.height() / 2;
1938 		}
1939 		if (phi & PGHDR_CLOCK) {
1940 			lString32 clock = getTimeString();
1941 			m_last_clock = clock;
1942 			int w = m_infoFont->getTextWidth(clock.c_str(), clock.length()) + 2;
1943 			m_infoFont->DrawTextString(drawbuf, info.right - w, iy,
1944 					clock.c_str(), clock.length(), U' ', NULL, false);
1945 			info.right -= w + info.height() / 2;
1946 		}
1947 		int authorsw = 0;
1948 		lString32 authors;
1949 		if (phi & PGHDR_AUTHOR)
1950 			authors = getAuthors();
1951 		int titlew = 0;
1952 		lString32 title;
1953 		if (phi & PGHDR_TITLE) {
1954 			title = getTitle();
1955 			if (title.empty() && authors.empty())
1956 				title = m_doc_props->getStringDef(DOC_PROP_FILE_NAME);
1957 			if (!title.empty())
1958 				titlew
1959 						= m_infoFont->getTextWidth(title.c_str(),
1960 								title.length());
1961 		}
1962 		if (phi & PGHDR_AUTHOR && !authors.empty()) {
1963 			if (!title.empty())
1964 				authors += U'.';
1965 			authorsw = m_infoFont->getTextWidth(authors.c_str(),
1966 					authors.length());
1967 		}
1968 		int w = info.width() - 10;
1969 		if (authorsw + titlew + 10 > w) {
1970 			if ((pageIndex & 1))
1971 				text = title;
1972 			else {
1973 				text = authors;
1974 				if (!text.empty() && text[text.length() - 1] == '.')
1975 					text = text.substr(0, text.length() - 1);
1976 			}
1977 		} else {
1978             text = authors + "  " + title;
1979 		}
1980 	}
1981 	lvRect newcr = headerRc;
1982 	newcr.right = info.right - 10;
1983 	drawbuf->SetClipRect(&newcr);
1984 	text = fitTextWidthWithEllipsis(text, m_infoFont, newcr.width());
1985 	if (!text.empty()) {
1986 		m_infoFont->DrawTextString(drawbuf, info.left, iy, text.c_str(),
1987 				text.length(), U' ', NULL, false);
1988 	}
1989 	drawbuf->SetClipRect(&oldcr);
1990 	//--------------
1991 	drawbuf->SetTextColor(getTextColor());
1992 }
1993 
drawPageTo(LVDrawBuf * drawbuf,LVRendPageInfo & page,lvRect * pageRect,int pageCount,int basePage)1994 void LVDocView::drawPageTo(LVDrawBuf * drawbuf, LVRendPageInfo & page,
1995 		lvRect * pageRect, int pageCount, int basePage) {
1996 	int start = page.start;
1997 	int height = page.height;
1998 	//CRLog::trace("drawPageTo(%d,%d)", start, height);
1999 	lvRect fullRect(0, 0, drawbuf->GetWidth(), drawbuf->GetHeight());
2000 	if (!pageRect)
2001 		pageRect = &fullRect;
2002     drawbuf->setHidePartialGlyphs(getViewMode() == DVM_PAGES);
2003 	//int offset = (pageRect->height() - m_pageMargins.top - m_pageMargins.bottom - height) / 3;
2004 	//if (offset>16)
2005 	//    offset = 16;
2006 	//if (offset<0)
2007 	//    offset = 0;
2008 	int offset = 0;
2009 	lvRect clip;
2010 	clip.top = pageRect->top + m_pageMargins.top + offset;
2011 	clip.bottom = pageRect->top + m_pageMargins.top + height + offset;
2012 	// clip.left = pageRect->left + m_pageMargins.left;
2013 	// clip.right = pageRect->left + pageRect->width() - m_pageMargins.right;
2014 	// We don't really need to enforce left and right clipping of page margins:
2015 	// this allows glyphs that need to (like 'J' at start of line or 'f' at
2016 	// end of line with some fonts) to not be cut by this clipping.
2017 	clip.left = pageRect->left;
2018 	clip.right = pageRect->left + pageRect->width();
2019 	if (page.flags & RN_PAGE_TYPE_COVER)
2020 		clip.top = pageRect->top + m_pageMargins.top;
2021 	if ( ( ((m_pageHeaderPos != PAGE_HEADER_POS_NONE && m_pageHeaderInfo) || !m_pageHeaderOverride.empty()) && (page.flags & RN_PAGE_TYPE_NORMAL) )
2022 				&& getViewMode() == DVM_PAGES ) {
2023 		int phi = m_pageHeaderInfo;
2024 		if (getVisiblePageCount() == 2) {
2025 			if (page.index & 1) {
2026 				// right
2027 				phi &= ~PGHDR_AUTHOR;
2028             } else {
2029 				// left
2030 				phi &= ~PGHDR_TITLE;
2031                 phi &= ~PGHDR_PERCENT;
2032                 phi &= ~PGHDR_PAGE_NUMBER;
2033 				phi &= ~PGHDR_PAGE_COUNT;
2034 				phi &= ~PGHDR_BATTERY;
2035 				phi &= ~PGHDR_CLOCK;
2036 			}
2037 		}
2038 		lvRect info;
2039 		getPageHeaderRectangle(page.index, info);
2040 		drawPageHeader(drawbuf, info, page.index - 1 + basePage, phi, pageCount
2041 				- 1 + basePage);
2042 		if (PAGE_HEADER_POS_TOP == m_pageHeaderPos) {
2043 			// only when page header at page header (but not page footer)
2044 			clip.top += info.height();
2045 			clip.bottom += info.height();
2046 		}
2047 	}
2048 	drawbuf->SetClipRect(&clip);
2049 	if (m_doc) {
2050 		if (page.flags & RN_PAGE_TYPE_COVER) {
2051 			lvRect rc = *pageRect;
2052 			drawbuf->SetClipRect(&rc);
2053 			//if ( m_pageMargins.bottom > m_pageMargins.top )
2054 			//    rc.bottom -= m_pageMargins.bottom - m_pageMargins.top;
2055 			/*
2056 			 rc.left += m_pageMargins.left / 2;
2057 			 rc.top += m_pageMargins.bottom / 2;
2058 			 rc.right -= m_pageMargins.right / 2;
2059 			 rc.bottom -= m_pageMargins.bottom / 2;
2060 			 */
2061 			//CRLog::trace("Entering drawCoverTo()");
2062 			drawCoverTo(drawbuf, rc);
2063 		} else {
2064 			// draw main page text
2065             if ( m_markRanges.length() )
2066                 CRLog::trace("Entering DrawDocument() : %d ranges", m_markRanges.length());
2067 			//CRLog::trace("Entering DrawDocument()");
2068 			if (page.height)
2069 				DrawDocument(*drawbuf, m_doc->getRootNode(), pageRect->left
2070 						+ m_pageMargins.left, clip.top, pageRect->width()
2071 						- m_pageMargins.left - m_pageMargins.right, height, 0,
2072                                                 -start + offset, m_dy, &m_markRanges, &m_bmkRanges);
2073 			//CRLog::trace("Done DrawDocument() for main text");
2074 			// draw footnotes
2075 #define FOOTNOTE_MARGIN_REM 1 // as in lvpagesplitter.cpp
2076 			int footnote_margin = FOOTNOTE_MARGIN_REM * gRootFontSize;
2077 			int fny = clip.top + (page.height ? page.height + footnote_margin
2078 					: footnote_margin);
2079 			// Try to push footnotes to the bottom of page if possible
2080 			int footnotes_height = 0;
2081 			for (int fn = 0; fn < page.footnotes.length(); fn++) {
2082 				footnotes_height += page.footnotes[fn].height;
2083 			}
2084 			if (footnotes_height > 0) {
2085 				int h_avail = m_dy - getPageHeaderHeight()
2086 						   - m_pageMargins.top - m_pageMargins.bottom
2087 						   - height - footnote_margin;
2088 				fny += h_avail - footnotes_height; // put empty space before first footnote
2089 			}
2090 			int fy = fny;
2091 			bool footnoteDrawed = false;
2092 			for (int fn = 0; fn < page.footnotes.length(); fn++) {
2093 				int fstart = page.footnotes[fn].start;
2094 				int fheight = page.footnotes[fn].height;
2095 				clip.top = fy + offset;
2096 				clip.bottom = fy + offset + fheight;
2097 				// Also avoid left and right clipping of page margins with footnotes
2098 				// clip.left = pageRect->left + m_pageMargins.left;
2099 				// clip.right = pageRect->right - m_pageMargins.right;
2100 				clip.left = pageRect->left;
2101 				clip.right = pageRect->right;
2102 				drawbuf->SetClipRect(&clip);
2103 				DrawDocument(*drawbuf, m_doc->getRootNode(), pageRect->left
2104 						+ m_pageMargins.left, fy + offset, pageRect->width()
2105 						- m_pageMargins.left - m_pageMargins.right, fheight, 0,
2106 						-fstart + offset, m_dy, &m_markRanges);
2107 				footnoteDrawed = true;
2108 				fy += fheight;
2109 			}
2110 			if (footnoteDrawed) { // && page.height
2111 				// Draw a small horizontal line as a separator inside
2112 				// the margin between text and footnotes
2113 				fny -= footnote_margin * 1/3;
2114 				drawbuf->SetClipRect(NULL);
2115                 lUInt32 cl = drawbuf->GetTextColor();
2116                 cl = (cl & 0xFFFFFF) | (0x55000000);
2117 				// The line separator was using the full page width:
2118 				//   int x1 = pageRect->right - m_pageMargins.right;
2119 				// but 1/7 of page width looks like what we can see in some books
2120 				int sep_width = (pageRect->right - pageRect->left) / 7;
2121 				int x0, x1;
2122 				if ( page.flags & RN_PAGE_FOOTNOTES_MOSTLY_RTL ) { // draw separator on the right
2123 					x1 = pageRect->right - m_pageMargins.right;
2124 					x0 = x1 - sep_width;
2125 				}
2126 				else {
2127 					x0 = pageRect->left + m_pageMargins.left;
2128 					x1 = x0 + sep_width;
2129 				}
2130 				drawbuf->FillRect(x0, fny, x1, fny+1, cl);
2131 			}
2132 		}
2133 	}
2134 	drawbuf->SetClipRect(NULL);
2135 #ifdef SHOW_PAGE_RECT
2136 	drawbuf->FillRect(pageRect->left, pageRect->top, pageRect->left+1, pageRect->bottom, 0xAAAAAA);
2137 	drawbuf->FillRect(pageRect->left, pageRect->top, pageRect->right, pageRect->top+1, 0xAAAAAA);
2138 	drawbuf->FillRect(pageRect->right-1, pageRect->top, pageRect->right, pageRect->bottom, 0xAAAAAA);
2139 	drawbuf->FillRect(pageRect->left, pageRect->bottom-1, pageRect->right, pageRect->bottom, 0xAAAAAA);
2140 	drawbuf->FillRect(pageRect->left+m_pageMargins.left, pageRect->top+m_pageMargins.top+headerHeight, pageRect->left+1+m_pageMargins.left, pageRect->bottom-m_pageMargins.bottom, 0x555555);
2141 	drawbuf->FillRect(pageRect->left+m_pageMargins.left, pageRect->top+m_pageMargins.top+headerHeight, pageRect->right-m_pageMargins.right, pageRect->top+1+m_pageMargins.top+headerHeight, 0x555555);
2142 	drawbuf->FillRect(pageRect->right-1-m_pageMargins.right, pageRect->top+m_pageMargins.top+headerHeight, pageRect->right-m_pageMargins.right, pageRect->bottom-m_pageMargins.bottom, 0x555555);
2143 	drawbuf->FillRect(pageRect->left+m_pageMargins.left, pageRect->bottom-1-m_pageMargins.bottom, pageRect->right-m_pageMargins.right, pageRect->bottom-m_pageMargins.bottom, 0x555555);
2144 #endif
2145 
2146 #if 0
2147 	lString32 pagenum = lString32::itoa( page.index+1 );
2148 	m_font->DrawTextString(drawbuf, 5, 0 , pagenum.c_str(), pagenum.length(), '?', NULL, false); //drawbuf->GetHeight()-m_font->getHeight()
2149 #endif
2150 }
2151 
2152 /// returns page count
getPageCount()2153 int LVDocView::getPageCount() {
2154 	return m_pages.length();
2155 }
2156 
2157 //============================================================================
2158 // Navigation code
2159 //============================================================================
2160 
2161 /// get position of view inside document
GetPos(lvRect & rc)2162 void LVDocView::GetPos(lvRect & rc) {
2163     checkPos();
2164 	rc.left = 0;
2165 	rc.right = GetWidth();
2166 	if (isPageMode() && _page >= 0 && _page < m_pages.length()) {
2167 		rc.top = m_pages[_page]->start;
2168         if (getVisiblePageCount() == 2) {
2169             if (_page < m_pages.length() - 1)
2170                 rc.bottom = m_pages[_page + 1]->start + m_pages[_page + 1]->height;
2171             else
2172                 rc.bottom = rc.top + m_pages[_page]->height;
2173         } else
2174             rc.bottom = rc.top + m_pages[_page]->height;
2175 	} else {
2176 		rc.top = _pos;
2177 		rc.bottom = _pos + GetHeight();
2178 	}
2179 }
2180 
getPageFlow(int pageIndex)2181 int LVDocView::getPageFlow(int pageIndex)
2182 {
2183 	if (pageIndex >= 0 && pageIndex < m_pages.length())
2184 		return m_pages[pageIndex]->flow;
2185 	return -1;
2186 }
2187 
hasNonLinearFlows()2188 bool LVDocView::hasNonLinearFlows()
2189 {
2190 	return m_pages.hasNonLinearFlows();
2191 }
2192 
getPageHeight(int pageIndex)2193 int LVDocView::getPageHeight(int pageIndex)
2194 {
2195 	if (isPageMode() && pageIndex >= 0 && pageIndex < m_pages.length())
2196 		return m_pages[pageIndex]->height;
2197 	return 0;
2198 }
2199 
getPageStartY(int pageIndex)2200 int LVDocView::getPageStartY(int pageIndex)
2201 {
2202 	if (isPageMode() && pageIndex >= 0 && pageIndex < m_pages.length())
2203 		return m_pages[pageIndex]->start;
2204 	return -1;
2205 }
2206 
2207 /// get vertical position of view inside document
GetPos()2208 int LVDocView::GetPos() {
2209     checkPos();
2210     if (isPageMode() && _page >= 0 && _page < m_pages.length())
2211 		return m_pages[_page]->start;
2212 	return _pos;
2213 }
2214 
SetPos(int pos,bool savePos,bool allowScrollAfterEnd)2215 int LVDocView::SetPos(int pos, bool savePos, bool allowScrollAfterEnd) {
2216 	LVLock lock(getMutex());
2217 	_posIsSet = true;
2218     CHECK_RENDER("setPos()")
2219 	//if ( m_posIsSet && m_pos==pos )
2220 	//    return;
2221 	if (isScrollMode()) {
2222 		if (pos > GetFullHeight() - m_dy && !allowScrollAfterEnd)
2223 			pos = GetFullHeight() - m_dy;
2224 		if (pos < 0)
2225 			pos = 0;
2226 		_pos = pos;
2227 		int page = m_pages.FindNearestPage(pos, 0);
2228 		if (page >= 0 && page < m_pages.length())
2229 			_page = page;
2230 		else
2231 			_page = -1;
2232 	} else {
2233 		int pc = getVisiblePageCount();
2234 		int page = m_pages.FindNearestPage(pos, 0);
2235 		if (pc == 2)
2236 			page &= ~1;
2237 		if (page < m_pages.length()) {
2238 			_pos = m_pages[page]->start;
2239 			_page = page;
2240 		} else {
2241 			_pos = 0;
2242 			_page = 0;
2243 		}
2244 	}
2245 	if (savePos)
2246 		_posBookmark = getBookmark();
2247 	_posIsSet = true;
2248 	updateScroll();
2249 	return 1;
2250 	//Draw();
2251 }
2252 
getCurPage()2253 int LVDocView::getCurPage() {
2254 	LVLock lock(getMutex());
2255 	checkPos();
2256 	if (isPageMode() && _page >= 0)
2257 		return _page;
2258 	return m_pages.FindNearestPage(_pos, 0);
2259 }
2260 
goToPage(int page,bool updatePosBookmark,bool regulateTwoPages)2261 bool LVDocView::goToPage(int page, bool updatePosBookmark, bool regulateTwoPages) {
2262 	LVLock lock(getMutex());
2263     CHECK_RENDER("goToPage()")
2264 	if (!m_pages.length())
2265 		return false;
2266 	bool res = true;
2267 	if (isScrollMode()) {
2268 		if (page >= 0 && page < m_pages.length()) {
2269 			_pos = m_pages[page]->start;
2270 			_page = page;
2271 		} else {
2272 			res = false;
2273 			_pos = 0;
2274 			_page = 0;
2275 		}
2276 	} else {
2277 		int pc = getVisiblePageCount();
2278 		if (page >= m_pages.length()) {
2279 			page = m_pages.length() - 1;
2280 			res = false;
2281 		}
2282 		if (page < 0) {
2283 			page = 0;
2284 			res = false;
2285 		}
2286 		if (pc == 2 && regulateTwoPages)
2287 			page &= ~1; // first page will always be odd (page are counted from 0)
2288 		if (page >= 0 && page < m_pages.length()) {
2289 			_pos = m_pages[page]->start;
2290 			_page = page;
2291 		} else {
2292 			_pos = 0;
2293 			_page = 0;
2294 			res = false;
2295 		}
2296 	}
2297     if (updatePosBookmark) {
2298         _posBookmark = getBookmark();
2299     }
2300 	_posIsSet = true;
2301 	updateScroll();
2302     //if (res)
2303         //updateBookMarksRanges();
2304 	return res;
2305 }
2306 
2307 /// returns true if time changed since clock has been last drawed
isTimeChanged()2308 bool LVDocView::isTimeChanged() {
2309 	if ( m_pageHeaderInfo & PGHDR_CLOCK ) {
2310 		bool res = (m_last_clock != getTimeString());
2311 		if (res)
2312 			clearImageCache();
2313 		return res;
2314 	}
2315 	return false;
2316 }
2317 
2318 /// check whether resize or creation of buffer is necessary, ensure buffer is ok
checkBufferSize(LVRef<LVColorDrawBuf> & buf,int dx,int dy)2319 static bool checkBufferSize( LVRef<LVColorDrawBuf> & buf, int dx, int dy ) {
2320     if ( buf.isNull() || buf->GetWidth()!=dx || buf->GetHeight()!=dy ) {
2321         buf.Clear();
2322         buf = LVRef<LVColorDrawBuf>( new LVColorDrawBuf(dx, dy, 16) );
2323         return false; // need redraw
2324     } else
2325         return true;
2326 }
2327 
2328 /// clears page background
drawPageBackground(LVDrawBuf & drawbuf,int offsetX,int offsetY,int alpha)2329 void LVDocView::drawPageBackground( LVDrawBuf & drawbuf, int offsetX, int offsetY, int alpha)
2330 {
2331     //CRLog::trace("drawPageBackground() called");
2332     drawbuf.SetBackgroundColor(m_backgroundColor);
2333     if ( !m_backgroundImage.isNull() ) {
2334         // texture
2335         int dx = drawbuf.GetWidth();
2336         int dy = drawbuf.GetHeight();
2337         if ( m_backgroundTiled ) {
2338             //CRLog::trace("drawPageBackground() using texture %d x %d", m_backgroundImage->GetWidth(), m_backgroundImage->GetHeight());
2339             if ( !checkBufferSize( m_backgroundImageScaled, m_backgroundImage->GetWidth(), m_backgroundImage->GetHeight() ) ) {
2340                 // unpack
2341 
2342                 m_backgroundImageScaled->Draw(LVCreateAlphaTransformImageSource(m_backgroundImage, alpha), 0, 0, m_backgroundImage->GetWidth(), m_backgroundImage->GetHeight(), false);
2343             }
2344             LVImageSourceRef src = LVCreateDrawBufImageSource(m_backgroundImageScaled.get(), false);
2345             LVImageSourceRef tile = LVCreateTileTransform( src, dx, dy, offsetX, offsetY );
2346             //CRLog::trace("created tile image, drawing");
2347             drawbuf.Draw(LVCreateAlphaTransformImageSource(tile, alpha), 0, 0, dx, dy);
2348             //CRLog::trace("draw completed");
2349         } else {
2350             if ( getViewMode()==DVM_SCROLL ) {
2351                 // scroll
2352                 if ( !checkBufferSize( m_backgroundImageScaled, dx, m_backgroundImage->GetHeight() ) ) {
2353                     // unpack
2354                     LVImageSourceRef resized = LVCreateStretchFilledTransform(m_backgroundImage, dx, m_backgroundImage->GetHeight(),
2355                                                                               IMG_TRANSFORM_STRETCH,
2356                                                                               IMG_TRANSFORM_TILE,
2357                                                                               0, 0);
2358                     m_backgroundImageScaled->Draw(LVCreateAlphaTransformImageSource(resized, alpha), 0, 0, dx, m_backgroundImage->GetHeight(), false);
2359                 }
2360                 LVImageSourceRef src = LVCreateDrawBufImageSource(m_backgroundImageScaled.get(), false);
2361                 LVImageSourceRef resized = LVCreateStretchFilledTransform(src, dx, dy,
2362                                                                           IMG_TRANSFORM_TILE,
2363                                                                           IMG_TRANSFORM_TILE,
2364                                                                           offsetX, offsetY);
2365                 drawbuf.Draw(LVCreateAlphaTransformImageSource(resized, alpha), 0, 0, dx, dy);
2366             } else if ( getVisiblePageCount() != 2 ) {
2367                 // single page
2368                 if ( !checkBufferSize( m_backgroundImageScaled, dx, dy ) ) {
2369                     // unpack
2370                     LVImageSourceRef resized = LVCreateStretchFilledTransform(m_backgroundImage, dx, dy,
2371                                                                               IMG_TRANSFORM_STRETCH,
2372                                                                               IMG_TRANSFORM_STRETCH,
2373                                                                               offsetX, offsetY);
2374                     m_backgroundImageScaled->Draw(LVCreateAlphaTransformImageSource(resized, alpha), 0, 0, dx, dy, false);
2375                 }
2376                 LVImageSourceRef src = LVCreateDrawBufImageSource(m_backgroundImageScaled.get(), false);
2377                 drawbuf.Draw(LVCreateAlphaTransformImageSource(src, alpha), 0, 0, dx, dy);
2378             } else {
2379                 // two pages
2380                 int halfdx = (dx + 1) / 2;
2381                 if ( !checkBufferSize( m_backgroundImageScaled, halfdx, dy ) ) {
2382                     // unpack
2383                     LVImageSourceRef resized = LVCreateStretchFilledTransform(m_backgroundImage, halfdx, dy,
2384                                                                               IMG_TRANSFORM_STRETCH,
2385                                                                               IMG_TRANSFORM_STRETCH,
2386                                                                               offsetX, offsetY);
2387                     m_backgroundImageScaled->Draw(LVCreateAlphaTransformImageSource(resized, alpha), 0, 0, halfdx, dy, false);
2388                 }
2389                 LVImageSourceRef src = LVCreateDrawBufImageSource(m_backgroundImageScaled.get(), false);
2390                 drawbuf.Draw(LVCreateAlphaTransformImageSource(src, alpha), 0, 0, halfdx, dy);
2391                 drawbuf.Draw(LVCreateAlphaTransformImageSource(src, alpha), dx/2, 0, dx - halfdx, dy);
2392             }
2393         }
2394     } else {
2395         // solid color
2396         lUInt32 cl = m_backgroundColor;
2397         if (alpha > 0) {
2398             cl = (cl & 0xFFFFFF) | (alpha << 24);
2399             drawbuf.FillRect(0, 0, drawbuf.GetWidth(), drawbuf.GetHeight(), cl);
2400         } else
2401             drawbuf.Clear(cl);
2402     }
2403     if (drawbuf.GetBitsPerPixel() == 32 && getVisiblePageCount() == 2) {
2404         int x = drawbuf.GetWidth() / 2;
2405         lUInt32 cl = m_backgroundColor;
2406         cl = ((cl & 0xFCFCFC) + 0x404040) >> 1;
2407         drawbuf.FillRect(x, 0, x + 1, drawbuf.GetHeight(), cl);
2408     }
2409 }
2410 
2411 /// draw to specified buffer
Draw(LVDrawBuf & drawbuf,int position,int page,bool rotate,bool autoresize)2412 void LVDocView::Draw(LVDrawBuf & drawbuf, int position, int page, bool rotate, bool autoresize) {
2413 	LVLock lock(getMutex());
2414 	//CRLog::trace("Draw() : calling checkPos()");
2415 	checkPos();
2416 	//CRLog::trace("Draw() : calling drawbuf.resize(%d, %d)", m_dx, m_dy);
2417 	if (autoresize)
2418 		drawbuf.Resize(m_dx, m_dy);
2419 	drawbuf.SetBackgroundColor(m_backgroundColor);
2420 	drawbuf.SetTextColor(m_textColor);
2421 	//CRLog::trace("Draw() : calling clear()", m_dx, m_dy);
2422 
2423 	if (!m_is_rendered)
2424 		return;
2425 	if (!m_doc)
2426 		return;
2427 	if (m_font.isNull())
2428 		return;
2429 	if (isScrollMode()) {
2430 		drawbuf.SetClipRect(NULL);
2431         drawbuf.setHidePartialGlyphs(false);
2432         drawPageBackground(drawbuf, 0, position);
2433         int cover_height = 0;
2434 		if (m_pages.length() > 0 && (m_pages[0]->flags & RN_PAGE_TYPE_COVER))
2435 			cover_height = m_pages[0]->height;
2436 		if (position < cover_height) {
2437 			lvRect rc;
2438 			drawbuf.GetClipRect(&rc);
2439 			rc.top -= position;
2440 			rc.bottom -= position;
2441 			rc.top += m_pageMargins.top;
2442 			rc.bottom -= m_pageMargins.bottom;
2443 			rc.left += m_pageMargins.left;
2444 			rc.right -= m_pageMargins.right;
2445 			drawCoverTo(&drawbuf, rc);
2446 		}
2447 		DrawDocument(drawbuf, m_doc->getRootNode(), m_pageMargins.left, 0, drawbuf.GetWidth()
2448 				- m_pageMargins.left - m_pageMargins.right, drawbuf.GetHeight(), 0, -position,
2449 				drawbuf.GetHeight(), &m_markRanges, &m_bmkRanges);
2450 	} else {
2451 		int pc = getVisiblePageCount();
2452 		//CRLog::trace("searching for page with offset=%d", position);
2453 		if (page == -1)
2454 			page = m_pages.FindNearestPage(position, 0);
2455 		//CRLog::trace("found page #%d", page);
2456 
2457         //drawPageBackground(drawbuf, (page * 1356) & 0xFFF, 0x1000 - (page * 1356) & 0xFFF);
2458         drawPageBackground(drawbuf, 0, 0);
2459 
2460         if (page >= 0 && page < m_pages.length())
2461 			drawPageTo(&drawbuf, *m_pages[page], &m_pageRects[0],
2462 					m_pages.length(), 1);
2463 		if (pc == 2 && page >= 0 && page + 1 < m_pages.length())
2464 			drawPageTo(&drawbuf, *m_pages[page + 1], &m_pageRects[1],
2465 					m_pages.length(), 1);
2466 	}
2467 #if CR_INTERNAL_PAGE_ORIENTATION==1
2468 	if ( rotate ) {
2469 		//CRLog::trace("Rotate drawing buffer. Src size=(%d, %d), angle=%d, buf(%d, %d)", m_dx, m_dy, m_rotateAngle, drawbuf.GetWidth(), drawbuf.GetHeight() );
2470 		drawbuf.Rotate( m_rotateAngle );
2471 		//CRLog::trace("Rotate done. buf(%d, %d)", drawbuf.GetWidth(), drawbuf.GetHeight() );
2472 	}
2473 #endif
2474 }
2475 
2476 //void LVDocView::Draw()
2477 //{
2478 //Draw( m_drawbuf, m_pos, true );
2479 //}
2480 
2481 /// converts point from window to document coordinates, returns true if success
windowToDocPoint(lvPoint & pt)2482 bool LVDocView::windowToDocPoint(lvPoint & pt) {
2483     CHECK_RENDER("windowToDocPoint()")
2484 #if CR_INTERNAL_PAGE_ORIENTATION==1
2485 	pt = rotatePoint( pt, true );
2486 #endif
2487 	if (getViewMode() == DVM_SCROLL) {
2488 		// SCROLL mode
2489 		pt.y += _pos;
2490 		pt.x -= m_pageMargins.left;
2491 		return true;
2492 	} else {
2493 		// PAGES mode
2494 		int page = getCurPage();
2495 		lvRect * rc = NULL;
2496 		lvRect page1(m_pageRects[0]);
2497 		int headerHeight = (PAGE_HEADER_POS_TOP == m_pageHeaderPos) ? getPageHeaderHeight() : 0;
2498 		page1.left += m_pageMargins.left;
2499 		page1.top += m_pageMargins.top + headerHeight;
2500 		page1.right -= m_pageMargins.right;
2501 		page1.bottom -= m_pageMargins.bottom;
2502 		lvRect page2;
2503 		if (page1.isPointInside(pt)) {
2504 			rc = &page1;
2505 		} else if (getVisiblePageCount() == 2) {
2506 			page2 = m_pageRects[1];
2507 			page2.left += m_pageMargins.left;
2508 			page2.top += m_pageMargins.top + headerHeight;
2509 			page2.right -= m_pageMargins.right;
2510 			page2.bottom -= m_pageMargins.bottom;
2511 			if (page2.isPointInside(pt)) {
2512 				rc = &page2;
2513 				page++;
2514 			}
2515 		}
2516 		if (rc && page >= 0 && page < m_pages.length()) {
2517 			int page_y = m_pages[page]->start;
2518 			pt.x -= rc->left;
2519 			pt.y -= rc->top;
2520 			if (pt.y < m_pages[page]->height) {
2521 				//CRLog::debug(" point page offset( %d, %d )", pt.x, pt.y );
2522 				pt.y += page_y;
2523 				return true;
2524 			}
2525 		}
2526 	}
2527 	return false;
2528 }
2529 
2530 /// converts point from document to window coordinates, returns true if success
docToWindowPoint(lvPoint & pt,bool isRectBottom,bool fitToPage)2531 bool LVDocView::docToWindowPoint(lvPoint & pt, bool isRectBottom, bool fitToPage) {
2532 	LVLock lock(getMutex());
2533     CHECK_RENDER("docToWindowPoint()")
2534 	// TODO: implement coordinate conversion here
2535 	if (getViewMode() == DVM_SCROLL) {
2536 		// SCROLL mode
2537 		pt.y -= _pos;
2538 		pt.x += m_pageMargins.left;
2539 		return true;
2540 	} else {
2541             // PAGES mode
2542             int page = getCurPage();
2543             int headerHeight = (PAGE_HEADER_POS_TOP == m_pageHeaderPos) ? getPageHeaderHeight() : 0;
2544             if (page >= 0 && page < m_pages.length() && pt.y >= m_pages[page]->start) {
2545                 int index = -1;
2546                 // The y at start+height is normally part of the next
2547                 // page. But we are often called twice with a topLeft
2548                 // and a bottomRight of a rectangle bounding words or
2549                 // a line, with the bottomRight being a rect height,
2550                 // so pointing to the y just outside the box.
2551                 // So, when we're specified that this point is a rect bottom,
2552                 // we return this page even if it's actually on next page.
2553                 int page_bottom = m_pages[page]->start + m_pages[page]->height;
2554                 if ( pt.y < page_bottom || (isRectBottom && pt.y == page_bottom) ) {
2555                     index = 0;
2556                 }
2557                 else if (getVisiblePageCount() == 2 && page + 1 < m_pages.length()) {
2558                     int next_page_bottom = m_pages[page + 1]->start + m_pages[page + 1]->height;
2559                     if ( pt.y < next_page_bottom || (isRectBottom && pt.y == next_page_bottom) ) {
2560                         index = 1;
2561                     }
2562                 }
2563                 if (index >= 0) {
2564                     /*
2565                     int x = pt.x + m_pageRects[index].left + m_pageMargins.left;
2566                     // We shouldn't get x ouside page width as we never crop on the
2567                     // width: if we do (if bug somewhere else), force it to be at the
2568                     // far right (this helps not discarding some highlights rects)
2569                     if (x >= m_pageRects[index].right - m_pageMargins.right) {
2570                         x = m_pageRects[index].right - m_pageMargins.right - 1;
2571                     }
2572                     if (x < m_pageRects[index].right - m_pageMargins.right) {
2573                         pt.x = x;
2574                         pt.y = pt.y + getPageHeaderHeight() + m_pageMargins.top - m_pages[page + index]->start;
2575                         return true;
2576                     }
2577                     */
2578                     // We don't crop on the left, so it feels like we don't need to
2579                     // ensure anything and crop on the right, and this allows text
2580                     // selection to grab bits of overflowed glyph
2581                     pt.x = pt.x + m_pageRects[index].left + m_pageMargins.left;
2582                     pt.y = pt.y + headerHeight + m_pageMargins.top - m_pages[page + index]->start;
2583                     return true;
2584                 }
2585             }
2586             if (fitToPage) {
2587                 // If we didn't find pt inside pages, adjust it to top or bottom page y
2588                 if (page >= 0 && page < m_pages.length() && pt.y < m_pages[page]->start) {
2589                     // Before 1st page top: adjust to page top
2590                     pt.x = pt.x + m_pageRects[0].left + m_pageMargins.left;
2591                     pt.y = headerHeight + m_pageMargins.top;
2592                 }
2593                 else if (getVisiblePageCount() == 2 && page + 1 < m_pages.length()
2594                         && pt.y >= m_pages[page + 1]->start + m_pages[page + 1]->height) {
2595                     // After 2nd page bottom: adjust to 2nd page bottom
2596                     pt.x = pt.x + m_pageRects[1].left + m_pageMargins.left;
2597                     pt.y = headerHeight + m_pageMargins.top + m_pages[page+1]->height;
2598                 }
2599                 else {
2600                     // After single page bottom: adjust to page bottom
2601                     pt.x = pt.x + m_pageRects[0].left + m_pageMargins.left;
2602                     pt.y = headerHeight + m_pageMargins.top + m_pages[page]->height;
2603                 }
2604                 return true;
2605             }
2606             return false;
2607 	}
2608 #if CR_INTERNAL_PAGE_ORIENTATION==1
2609 	pt = rotatePoint( pt, false );
2610 #endif
2611 	return false;
2612 }
2613 
2614 /// returns xpointer for specified window point
getNodeByPoint(lvPoint pt,bool strictBounds)2615 ldomXPointer LVDocView::getNodeByPoint(lvPoint pt, bool strictBounds) {
2616 	LVLock lock(getMutex());
2617     CHECK_RENDER("getNodeByPoint()")
2618 	if (windowToDocPoint(pt) && m_doc) {
2619 		ldomXPointer ptr = m_doc->createXPointer(pt, PT_DIR_EXACT, strictBounds);
2620 		//CRLog::debug("  ptr (%d, %d) node=%08X offset=%d", pt.x, pt.y, (lUInt32)ptr.getNode(), ptr.getOffset() );
2621 		return ptr;
2622 	}
2623 	return ldomXPointer();
2624 }
2625 
2626 /// returns image source for specified window point, if point is inside image
getImageByPoint(lvPoint pt)2627 LVImageSourceRef LVDocView::getImageByPoint(lvPoint pt) {
2628     LVImageSourceRef res = LVImageSourceRef();
2629     ldomXPointer ptr = getNodeByPoint(pt);
2630     if (ptr.isNull())
2631         return res;
2632     //CRLog::debug("node: %s", LCSTR(ptr.toString()));
2633     ldomNode* node = ptr.getNode();
2634     if (node)
2635         res = node->getObjectImageSource();
2636     if (!res.isNull())
2637         CRLog::debug("getImageByPoint(%d, %d) : found image %d x %d", pt.x, pt.y, res->GetWidth(), res->GetHeight());
2638     return res;
2639 }
2640 
drawImage(LVDrawBuf * buf,LVImageSourceRef img,int x,int y,int dx,int dy)2641 bool LVDocView::drawImage(LVDrawBuf * buf, LVImageSourceRef img, int x, int y, int dx, int dy)
2642 {
2643     if (img.isNull() || !buf)
2644         return false;
2645     // clear background
2646     drawPageBackground(*buf, 0, 0);
2647     // draw image
2648     buf->Draw(img, x, y, dx, dy, true);
2649     return true;
2650 }
2651 
updateLayout()2652 void LVDocView::updateLayout() {
2653 	lvRect rc(0, 0, m_dx, m_dy);
2654 	m_pageRects[0] = rc;
2655 	m_pageRects[1] = rc;
2656 	if (getVisiblePageCount() == 2) {
2657 		int middle = (rc.left + rc.right) >> 1;
2658 		// m_pageRects[0].right = middle - m_pageMargins.right / 2;
2659 		// m_pageRects[1].left = middle + m_pageMargins.left / 2;
2660 		// ^ seems wrong, as we later add the margins to these m_pageRects
2661 		// so this would add to much uneeded middle margin
2662                 // We will ensure a max middle margin the size of a single
2663                 // left or right margin, whichever is the greatest.
2664 		// We still want to ensure a minimal middle margin in case
2665 		// the requested pageMargins are really small. Say 1.2em.
2666 		int min_middle_margin = 1.2 * m_font_size;
2667 		int max_middle_margin = m_pageMargins.left > m_pageMargins.right ? m_pageMargins.left : m_pageMargins.right;
2668 		int additional_middle_margin = 0;
2669 		int middle_margin = m_pageMargins.right + m_pageMargins.left;
2670 		if (middle_margin < min_middle_margin) {
2671 			additional_middle_margin = min_middle_margin - middle_margin;
2672 		}
2673 		else if (middle_margin > max_middle_margin) {
2674 			if (max_middle_margin > min_middle_margin)
2675 				additional_middle_margin = max_middle_margin - middle_margin; // negative
2676 			else
2677 				additional_middle_margin = min_middle_margin - middle_margin; // negative
2678 		}
2679 		// Note: with negative values, we allow these 2 m_pageRects to
2680 		// overlap. But it seems there is no issue doing that.
2681 		m_pageRects[0].right = middle - additional_middle_margin / 2;
2682 		m_pageRects[1].left = middle + additional_middle_margin / 2;
2683 	}
2684 }
2685 
2686 /// set list of icons to display at left side of header
setHeaderIcons(LVRefVec<LVImageSource> icons)2687 void LVDocView::setHeaderIcons(LVRefVec<LVImageSource> icons) {
2688 	m_headerIcons = icons;
2689 }
2690 
2691 /// get page document range, -1 for current page
getPageDocumentRange(int pageIndex)2692 LVRef<ldomXRange> LVDocView::getPageDocumentRange(int pageIndex) {
2693     LVLock lock(getMutex());
2694     CHECK_RENDER("getPageDocRange()")
2695     // On some pages (eg: that ends with some padding between an
2696     // image on this page, and some text on next page), there may
2697     // be some area which is rendered "final" without any content,
2698     // thus holding no node. We could then get a null 'start' or
2699     // 'end' below by looking only at start_y or end_y.
2700     // So, in all cases, loop while increasing or decreasing y
2701     // to get more chances of finding a valid XPointer.
2702     LVRef < ldomXRange > res(NULL);
2703     int start_y;
2704     int end_y;
2705     if (isScrollMode()) {
2706         // SCROLL mode
2707         start_y = _pos;
2708         end_y = _pos + m_dy;
2709         int fh = GetFullHeight();
2710         if (end_y >= fh)
2711             end_y = fh - 1;
2712     }
2713     else {
2714         // PAGES mode
2715         if (pageIndex < 0 || pageIndex >= m_pages.length())
2716             pageIndex = getCurPage();
2717         if (pageIndex >= 0 && pageIndex < m_pages.length()) {
2718             LVRendPageInfo * page = m_pages[pageIndex];
2719             if (page->flags & RN_PAGE_TYPE_COVER)
2720                 return res;
2721             start_y = page->start;
2722             end_y = page->start + page->height;
2723         }
2724         else {
2725             return res;
2726         }
2727     }
2728     if (!res.isNull())
2729         return res;
2730     int height = end_y - start_y;
2731     if (height < 0)
2732         return res;
2733     // Note: this may return a range that may not include some parts
2734     // of the document that are actually displayed on that page, like
2735     // floats that were in some HTML fragment of the previous page,
2736     // but whose positioning has been shifted/delayed and ended up
2737     // on this page (so, getCurrentPageLinks() may miss links from
2738     // such floats).
2739     // With floats, a page may actually show more than one range,
2740     // not sure how to deal with that (return a range including all
2741     // subranges, so possibly including stuff not shown on the page?)
2742     // We also want to get the xpointer to the first or last node
2743     // on a line in "logical order" instead of "visual order", which
2744     // is needed with bidi text to not miss some text on the first
2745     // or last line of the page.
2746     ldomXPointer start;
2747     ldomXPointer end;
2748     int start_h;
2749     for (start_h=0; start_h < height; start_h++) {
2750         start = m_doc->createXPointer(lvPoint(0, start_y + start_h), PT_DIR_SCAN_FORWARD_LOGICAL_FIRST);
2751         // printf("  start (%d=%d): %s\n", start_h, start_y + start_h, UnicodeToLocal(start.toString()).c_str());
2752         if (!start.isNull()) {
2753             // Check what we got is really in current page
2754             lvPoint pt = start.toPoint();
2755             // printf("start pt.y %d start_y %d end_y %d\n", pt.y, start_y, end_y);
2756             if (pt.y >= start_y && pt.y <= end_y)
2757                 break; // we found our start of page xpointer
2758         }
2759     }
2760     int end_h;
2761     for (end_h=height; end_h >= start_h; end_h--) {
2762         end = m_doc->createXPointer(lvPoint(GetWidth(), start_y + end_h), PT_DIR_SCAN_BACKWARD_LOGICAL_LAST);
2763             // (x=GetWidth() might be redundant with PT_DIR_SCAN_BACKWARD_LOGICAL_LAST, but it might help skiping floats)
2764         // printf("  end (%d=%d): %s\n", end_h, start_y + end_h, UnicodeToLocal(end.toString()).c_str());
2765         if (!end.isNull()) {
2766             // Check what we got is really in current page
2767             lvPoint pt = end.toPoint();
2768             // printf("end pt.y %d start_y %d end_y %d\n", pt.y, start_y, end_y);
2769             if (pt.y >= start_y && pt.y <= end_y)
2770                 break; // we found our end of page xpointer
2771         }
2772     }
2773     if (start.isNull() || end.isNull())
2774         return res;
2775     res = LVRef<ldomXRange> (new ldomXRange(start, end));
2776     return res;
2777 }
2778 
2779 /// returns number of non-space characters on current page
getCurrentPageCharCount()2780 int LVDocView::getCurrentPageCharCount()
2781 {
2782     lString32 text = getPageText(true);
2783     int count = 0;
2784     for (int i=0; i<text.length(); i++) {
2785         lChar32 ch = text[i];
2786         if (ch>='0')
2787             count++;
2788     }
2789     return count;
2790 }
2791 
2792 /// returns number of images on current page
getCurrentPageImageCount()2793 int LVDocView::getCurrentPageImageCount()
2794 {
2795     CHECK_RENDER("getCurPageImgCount()")
2796     LVRef<ldomXRange> range = getPageDocumentRange(-1);
2797     return getPageImageCount(range);
2798 }
2799 
getPageImageCount(LVRef<ldomXRange> & range)2800 int LVDocView::getPageImageCount(LVRef<ldomXRange>& range)
2801 {
2802     class ImageCounter : public ldomNodeCallback {
2803         int count;
2804     public:
2805         int get() { return count; }
2806         ImageCounter() : count(0) { }
2807         /// called for each found text fragment in range
2808         virtual void onText(ldomXRange *) { }
2809         /// called for each found node in range
2810         virtual bool onElement(ldomXPointerEx * ptr) {
2811             lString32 nodeName = ptr->getNode()->getNodeName();
2812             if (nodeName == "img" || nodeName == "image")
2813                 count++;
2814             return true;
2815         }
2816 
2817     };
2818     ImageCounter cnt;
2819     if (!range.isNull())
2820         range->forEach(&cnt);
2821     return cnt.get();
2822 }
2823 
2824 /// get page text, -1 for current page
getPageText(bool,int pageIndex)2825 lString32 LVDocView::getPageText(bool, int pageIndex) {
2826 	LVLock lock(getMutex());
2827     CHECK_RENDER("getPageText()")
2828 	lString32 txt;
2829 	LVRef < ldomXRange > range = getPageDocumentRange(pageIndex);
2830 	if (!range.isNull())
2831 		txt = range->getRangeText();
2832 	return txt;
2833 }
2834 
setRenderProps(int dx,int dy)2835 void LVDocView::setRenderProps(int dx, int dy) {
2836 	if (!m_doc || m_doc->getRootNode() == NULL)
2837 		return;
2838 	updateLayout();
2839 	m_showCover = !getCoverPageImage().isNull();
2840 
2841 	lString8 fontName = lString8(DEFAULT_FONT_NAME);
2842 	m_font_size = scaleFontSizeForDPI(m_requested_font_size);
2843 	gRootFontSize = m_font_size; // stored as global (for 'rem' css unit)
2844 	m_font = fontMan->GetFont(m_font_size, 400 + LVRendGetFontEmbolden(),
2845 			false, DEFAULT_FONT_FAMILY, m_defaultFontFace);
2846 	//m_font = LVCreateFontTransform( m_font, LVFONT_TRANSFORM_EMBOLDEN );
2847 	m_infoFont = fontMan->GetFont(m_status_font_size, 400, false,
2848 			DEFAULT_FONT_FAMILY, m_statusFontFace);
2849 	if (!m_font || !m_infoFont)
2850 		return;
2851 
2852 	if (dx == 0)
2853 		dx = m_pageRects[0].width() - m_pageMargins.left - m_pageMargins.right;
2854 	if (dy == 0)
2855 		dy = m_pageRects[0].height() - m_pageMargins.top - m_pageMargins.bottom
2856 				- getPageHeaderHeight();
2857 
2858     updateDocStyleSheet();
2859 
2860     m_doc->setRenderProps(dx, dy, m_showCover, m_showCover ? dy
2861             + m_pageMargins.bottom * 4 : 0, m_font, m_def_interline_space, m_props);
2862     text_highlight_options_t h;
2863     h.bookmarkHighlightMode = m_props->getIntDef(PROP_HIGHLIGHT_COMMENT_BOOKMARKS, highlight_mode_underline);
2864     h.selectionColor = (m_props->getColorDef(PROP_HIGHLIGHT_SELECTION_COLOR, 0xC0C0C0) & 0xFFFFFF);
2865     h.commentColor = (m_props->getColorDef(PROP_HIGHLIGHT_BOOKMARK_COLOR_COMMENT, 0xA08000) & 0xFFFFFF);
2866     h.correctionColor = (m_props->getColorDef(PROP_HIGHLIGHT_BOOKMARK_COLOR_CORRECTION, 0xA00000) & 0xFFFFFF);
2867     m_doc->setHightlightOptions(h);
2868 }
2869 
Render(int dx,int dy,LVRendPageList * pages)2870 void LVDocView::Render(int dx, int dy, LVRendPageList * pages) {
2871 	LVLock lock(getMutex());
2872 	{
2873 		if (!m_doc || m_doc->getRootNode() == NULL)
2874 			return;
2875 
2876 		if (dx == 0)
2877 			dx = m_pageRects[0].width() - m_pageMargins.left
2878 					- m_pageMargins.right;
2879 		if (dy == 0)
2880 			dy = m_pageRects[0].height() - m_pageMargins.top
2881 					- m_pageMargins.bottom - getPageHeaderHeight();
2882 
2883 		setRenderProps(dx, dy);
2884 
2885 		if (pages == NULL)
2886 			pages = &m_pages;
2887 
2888 		if (!m_font || !m_infoFont)
2889 			return;
2890 
2891         CRLog::debug("Render(width=%d, height=%d, fontSize=%d, currentFontSize=%d, 0 char width=%d)", dx, dy,
2892                      m_font_size, m_font->getSize(), m_font->getCharWidth('0'));
2893 		//CRLog::trace("calling render() for document %08X font=%08X", (unsigned int)m_doc, (unsigned int)m_font.get() );
2894 		bool did_rerender = m_doc->render(pages, isDocumentOpened() ? m_callback : NULL, dx, dy,
2895 					m_showCover, m_showCover ? dy + m_pageMargins.bottom * 4 : 0,
2896 					m_font, m_def_interline_space, m_props,
2897 					m_pageMargins.left, m_pageMargins.right);
2898 
2899 #if 0
2900                 // For debugging lvpagesplitter.cpp (small books)
2901                 for (int i=0; i<m_pages.length(); i++) {
2902                     printf("%4d:   %7d .. %-7d [%d]\n", i, m_pages[i]->start, m_pages[i]->start+m_pages[i]->height, m_pages[i]->height);
2903                 }
2904 #endif
2905 
2906 #if 0
2907                 // For debugging lvpagesplitter.cpp (larger books)
2908 		FILE * f = fopen("pagelist.log", "wt");
2909 		if (f) {
2910 			for (int i=0; i<m_pages.length(); i++)
2911 			{
2912 				fprintf(f, "%4d:   %7d .. %-7d [%d]\n", i, m_pages[i].start, m_pages[i].start+m_pages[i].height, m_pages[i].height);
2913 			}
2914 			fclose(f);
2915 		}
2916 #endif
2917 		if ( did_rerender ) {
2918 			m_section_bounds_valid = false;
2919 			fontMan->gc();
2920 		}
2921 		m_is_rendered = true;
2922 		//CRLog::debug("Making TOC...");
2923 		//makeToc();
2924 		CRLog::debug("Updating selections...");
2925 		updateSelections();
2926 		CRLog::debug("Render is finished");
2927 
2928 		if (!m_swapDone) {
2929 			int fs = m_doc_props->getIntDef(DOC_PROP_FILE_SIZE, 0);
2930 			int mfs = m_props->getIntDef(PROP_MIN_FILE_SIZE_TO_CACHE,
2931 					DOCUMENT_CACHING_SIZE_THRESHOLD);
2932 			CRLog::info(
2933 					"Check whether to swap: file size = %d, min size to cache = %d",
2934 					fs, mfs);
2935 			if (fs >= mfs) {
2936                 CRTimerUtil timeout(100); // 0.1 seconds
2937                 swapToCache(timeout);
2938                 m_swapDone = true;
2939             }
2940 		}
2941 
2942         updateBookMarksRanges();
2943 	}
2944 }
2945 
2946 /// sets selection for whole element, clears previous selection
selectElement(ldomNode * elem)2947 void LVDocView::selectElement(ldomNode * elem) {
2948 	ldomXRangeList & sel = getDocument()->getSelections();
2949 	sel.clear();
2950 	sel.add(new ldomXRange(elem));
2951 	updateSelections();
2952 }
2953 
2954 /// sets selection for list of words, clears previous selection
selectWords(const LVArray<ldomWord> & words)2955 void LVDocView::selectWords(const LVArray<ldomWord> & words) {
2956 	ldomXRangeList & sel = getDocument()->getSelections();
2957 	sel.clear();
2958 	sel.addWords(words);
2959 	updateSelections();
2960 }
2961 
2962 /// sets selections for ranges, clears previous selections
selectRanges(ldomXRangeList & ranges)2963 void LVDocView::selectRanges(ldomXRangeList & ranges) {
2964     ldomXRangeList & sel = getDocument()->getSelections();
2965     if (sel.empty() && ranges.empty())
2966         return;
2967     sel.clear();
2968     for (int i=0; i<ranges.length(); i++) {
2969         ldomXRange * item = ranges[i];
2970         sel.add(new ldomXRange(*item));
2971     }
2972     updateSelections();
2973 }
2974 
2975 /// sets selection for range, clears previous selection
selectRange(const ldomXRange & range)2976 void LVDocView::selectRange(const ldomXRange & range) {
2977     // LVE:DEBUG
2978 //    ldomXRange range2(range);
2979 //    CRLog::trace("selectRange( %s, %s )", LCSTR(range2.getStart().toString()), LCSTR(range2.getEnd().toString()) );
2980 	ldomXRangeList & sel = getDocument()->getSelections();
2981 	if (sel.length() == 1) {
2982 		if (range == *sel[0])
2983 			return; // the same range is set
2984 	}
2985 	sel.clear();
2986 	sel.add(new ldomXRange(range));
2987 	updateSelections();
2988 }
2989 
2990 /// clears selection
clearSelection()2991 void LVDocView::clearSelection() {
2992 	ldomXRangeList & sel = getDocument()->getSelections();
2993 	sel.clear();
2994 	updateSelections();
2995 }
2996 
2997 /// selects first link on page, if any. returns selected link range, null if no links.
selectFirstPageLink()2998 ldomXRange * LVDocView::selectFirstPageLink() {
2999 	ldomXRangeList list;
3000 	getCurrentPageLinks(list);
3001 	if (!list.length())
3002 		return NULL;
3003 	//
3004 	selectRange(*list[0]);
3005 	//
3006 	ldomXRangeList & sel = getDocument()->getSelections();
3007 	updateSelections();
3008 	return sel[0];
3009 }
3010 
3011 /// selects link on page, if any (delta==0 - current, 1-next, -1-previous). returns selected link range, null if no links.
selectPageLink(int delta,bool wrapAround)3012 ldomXRange * LVDocView::selectPageLink(int delta, bool wrapAround) {
3013 	ldomXRangeList & sel = getDocument()->getSelections();
3014 	ldomXRangeList list;
3015 	getCurrentPageLinks(list);
3016 	if (!list.length())
3017 		return NULL;
3018 	int currentLinkIndex = -1;
3019 	if (sel.length() > 0) {
3020 		ldomNode * currSel = sel[0]->getStart().getNode();
3021 		for (int i = 0; i < list.length(); i++) {
3022 			if (list[i]->getStart().getNode() == currSel) {
3023 				currentLinkIndex = i;
3024 				break;
3025 			}
3026 		}
3027 	}
3028 	bool error = false;
3029 	if (delta == 1) {
3030 		// next
3031 		currentLinkIndex++;
3032 		if (currentLinkIndex >= list.length()) {
3033 			if (wrapAround)
3034 				currentLinkIndex = 0;
3035 			else
3036 				error = true;
3037 		}
3038 
3039 	} else if (delta == -1) {
3040 		// previous
3041 		if (currentLinkIndex == -1)
3042 			currentLinkIndex = list.length() - 1;
3043 		else
3044 			currentLinkIndex--;
3045 		if (currentLinkIndex < 0) {
3046 			if (wrapAround)
3047 				currentLinkIndex = list.length() - 1;
3048 			else
3049 				error = true;
3050 		}
3051 	} else {
3052 		// current
3053 		if (currentLinkIndex < 0 || currentLinkIndex >= list.length())
3054 			error = true;
3055 	}
3056 	if (error) {
3057 		clearSelection();
3058 		return NULL;
3059 	}
3060 	//
3061 	selectRange(*list[currentLinkIndex]);
3062 	//
3063 	updateSelections();
3064 	return sel[0];
3065 }
3066 
3067 /// selects next link on page, if any. returns selected link range, null if no links.
selectNextPageLink(bool wrapAround)3068 ldomXRange * LVDocView::selectNextPageLink(bool wrapAround) {
3069 	return selectPageLink(+1, wrapAround);
3070 }
3071 
3072 /// selects previous link on page, if any. returns selected link range, null if no links.
selectPrevPageLink(bool wrapAround)3073 ldomXRange * LVDocView::selectPrevPageLink(bool wrapAround) {
3074 	return selectPageLink(-1, wrapAround);
3075 }
3076 
3077 /// returns selected link on page, if any. null if no links.
getCurrentPageSelectedLink()3078 ldomXRange * LVDocView::getCurrentPageSelectedLink() {
3079 	return selectPageLink(0, false);
3080 }
3081 
3082 /// get document rectangle for specified cursor position, returns false if not visible
getCursorDocRect(ldomXPointer ptr,lvRect & rc)3083 bool LVDocView::getCursorDocRect(ldomXPointer ptr, lvRect & rc) {
3084 	rc.clear();
3085 	if (ptr.isNull())
3086 		return false;
3087 	if (!ptr.getRect(rc)) {
3088 		rc.clear();
3089 		return false;
3090 	}
3091 	return true;
3092 }
3093 
3094 /// get screen rectangle for specified cursor position, returns false if not visible
getCursorRect(ldomXPointer ptr,lvRect & rc,bool scrollToCursor)3095 bool LVDocView::getCursorRect(ldomXPointer ptr, lvRect & rc,
3096 		bool scrollToCursor) {
3097 	if (!getCursorDocRect(ptr, rc))
3098 		return false;
3099 	for (;;) {
3100 
3101 		lvPoint topLeft = rc.topLeft();
3102 		lvPoint bottomRight = rc.bottomRight();
3103 		if (docToWindowPoint(topLeft) && docToWindowPoint(bottomRight)) {
3104 			rc.setTopLeft(topLeft);
3105 			rc.setBottomRight(bottomRight);
3106 			return true;
3107 		}
3108 		// try to scroll and convert doc->window again
3109 		if (!scrollToCursor)
3110 			break;
3111 		// scroll
3112 		goToBookmark(ptr);
3113 		scrollToCursor = false;
3114 	};
3115 	rc.clear();
3116 	return false;
3117 }
3118 
3119 /// follow link, returns true if navigation was successful
goLink(lString32 link,bool savePos)3120 bool LVDocView::goLink(lString32 link, bool savePos) {
3121 	CRLog::debug("goLink(%s)", LCSTR(link));
3122 	ldomNode * element = NULL;
3123 	if (link.empty()) {
3124 		ldomXRange * node = LVDocView::getCurrentPageSelectedLink();
3125 		if (node) {
3126 			link = node->getHRef();
3127 			ldomNode * p = node->getStart().getNode();
3128 			if (p->isText())
3129 				p = p->getParentNode();
3130 			element = p;
3131 		}
3132 		if (link.empty())
3133 			return false;
3134 	}
3135 	if (link[0] != '#' || link.length() <= 1) {
3136 		lString32 filename = link;
3137 		lString32 id;
3138         int p = filename.pos("#");
3139 		if (p >= 0) {
3140 			// split filename and anchor
3141 			// part1.html#chapter3 =>   part1.html & chapter3
3142 			id = filename.substr(p + 1);
3143 			filename = filename.substr(0, p);
3144 		}
3145         if (filename.pos(":") >= 0) {
3146 			// URL with protocol like http://
3147 			if (m_callback) {
3148 				m_callback->OnExternalLink(link, element);
3149 				return true;
3150 			}
3151 		} else {
3152 			// otherwise assume link to another file
3153 			CRLog::debug("Link to another file: %s   anchor=%s", UnicodeToUtf8(
3154 					filename).c_str(), UnicodeToUtf8(id).c_str());
3155 
3156 			lString32 baseDir = m_doc_props->getStringDef(DOC_PROP_FILE_PATH,
3157 					".");
3158 			LVAppendPathDelimiter(baseDir);
3159 			lString32 fn = m_doc_props->getStringDef(DOC_PROP_FILE_NAME, "");
3160 			CRLog::debug("Current path: %s   filename:%s", UnicodeToUtf8(
3161 					baseDir).c_str(), UnicodeToUtf8(fn).c_str());
3162 			baseDir = LVExtractPath(baseDir + fn);
3163 			//lString32 newPathName = LVMakeRelativeFilename( baseDir, filename );
3164 			lString32 newPathName = LVCombinePaths(baseDir, filename);
3165 			lString32 dir = LVExtractPath(newPathName);
3166 			lString32 filename = LVExtractFilename(newPathName);
3167 			LVContainerRef container = m_container;
3168 			lString32 arcname =
3169 					m_doc_props->getStringDef(DOC_PROP_ARC_NAME, "");
3170 			if (arcname.empty()) {
3171 				container = LVOpenDirectory(dir.c_str());
3172 				if (container.isNull())
3173 					return false;
3174 			} else {
3175 				filename = newPathName;
3176 				dir.clear();
3177 			}
3178 			CRLog::debug("Base dir: %s newPathName=%s",
3179 					UnicodeToUtf8(baseDir).c_str(),
3180 					UnicodeToUtf8(newPathName).c_str());
3181 
3182 			LVStreamRef stream = container->OpenStream(filename.c_str(),
3183 					LVOM_READ);
3184 			if (stream.isNull()) {
3185 				CRLog::error("Go to link: cannot find file %s", UnicodeToUtf8(
3186 						filename).c_str());
3187 				return false;
3188 			}
3189 			CRLog::info("Go to link: file %s is found",
3190 					UnicodeToUtf8(filename).c_str());
3191 			// return point
3192 			if (savePos)
3193 				savePosToNavigationHistory();
3194 
3195 			// close old document
3196 			savePosition();
3197 			clearSelection();
3198 			_posBookmark = ldomXPointer();
3199 			m_is_rendered = false;
3200 			m_swapDone = false;
3201 			_pos = 0;
3202 			_page = 0;
3203 			m_section_bounds_valid = false;
3204 			m_doc_props->setString(DOC_PROP_FILE_PATH, dir);
3205 			m_doc_props->setString(DOC_PROP_FILE_NAME, filename);
3206 			m_doc_props->setString(DOC_PROP_CODE_BASE, LVExtractPath(filename));
3207 			m_doc_props->setString(DOC_PROP_FILE_SIZE, lString32::itoa(
3208 					(int) stream->GetSize()));
3209             m_doc_props->setHex(DOC_PROP_FILE_CRC32, stream->getcrc32());
3210 			// TODO: load document from stream properly
3211 			if (!loadDocumentInt(stream)) {
3212                 createDefaultDocument(cs32("Load error"), lString32(
3213                         "Cannot open file ") + filename);
3214 				return false;
3215 			}
3216 			//m_filename = newPathName;
3217 			m_stream = stream;
3218 			m_container = container;
3219 
3220 			//restorePosition();
3221 
3222 			// TODO: setup properties
3223 			// go to anchor
3224 			if (!id.empty())
3225                 goLink(cs32("#") + id);
3226 			clearImageCache();
3227 			requestRender();
3228 			return true;
3229 		}
3230 		return false; // only internal links supported (started with #)
3231 	}
3232 	link = link.substr(1, link.length() - 1);
3233 	lUInt32 id = m_doc->getAttrValueIndex(link.c_str());
3234 	ldomNode * dest = m_doc->getNodeById(id);
3235 	if (!dest)
3236 		return false;
3237 	savePosToNavigationHistory();
3238 	ldomXPointer newPos(dest, 0);
3239 	goToBookmark(newPos);
3240         updateBookMarksRanges();
3241 	return true;
3242 }
3243 
3244 /// follow selected link, returns true if navigation was successful
goSelectedLink()3245 bool LVDocView::goSelectedLink() {
3246 	ldomXRange * link = getCurrentPageSelectedLink();
3247 	if (!link)
3248 		return false;
3249 	lString32 href = link->getHRef();
3250 	if (href.empty())
3251 		return false;
3252 	return goLink(href);
3253 }
3254 
3255 #define NAVIGATION_FILENAME_SEPARATOR U":"
splitNavigationPos(lString32 pos,lString32 & fname,lString32 & path)3256 bool splitNavigationPos(lString32 pos, lString32 & fname, lString32 & path) {
3257 	int p = pos.pos(lString32(NAVIGATION_FILENAME_SEPARATOR));
3258 	if (p <= 0) {
3259         fname = lString32::empty_str;
3260 		path = pos;
3261 		return false;
3262 	}
3263 	fname = pos.substr(0, p);
3264 	path = pos.substr(p + 1);
3265 	return true;
3266 }
3267 
3268 /// packs current file path and name
getNavigationPath()3269 lString32 LVDocView::getNavigationPath() {
3270 	lString32 fname = m_doc_props->getStringDef(DOC_PROP_FILE_NAME, "");
3271 	lString32 fpath = m_doc_props->getStringDef(DOC_PROP_FILE_PATH, "");
3272 	LVAppendPathDelimiter(fpath);
3273 	lString32 s = fpath + fname;
3274 	if (!m_arc.isNull())
3275         s = cs32("/") + s;
3276 	return s;
3277 }
3278 
3279 /// saves position to navigation history, to be able return back
savePosToNavigationHistory(lString32 path)3280 bool LVDocView::savePosToNavigationHistory(lString32 path) {
3281     if (!path.empty()) {
3282         lString32 s = getNavigationPath() + NAVIGATION_FILENAME_SEPARATOR
3283                 + path;
3284         CRLog::debug("savePosToNavigationHistory(%s)",
3285                 UnicodeToUtf8(s).c_str());
3286         return _navigationHistory.save(s);
3287     }
3288     return false;
3289 }
3290 
savePosToNavigationHistory()3291 bool LVDocView::savePosToNavigationHistory() {
3292 	ldomXPointer bookmark = getBookmark();
3293 	if (!bookmark.isNull()) {
3294 		lString32 path = bookmark.toString();
3295         return savePosToNavigationHistory(path);
3296 	}
3297 	return false;
3298 }
3299 
3300 /// navigate to history path URL
navigateTo(lString32 historyPath)3301 bool LVDocView::navigateTo(lString32 historyPath) {
3302 	CRLog::debug("navigateTo(%s)", LCSTR(historyPath));
3303 	lString32 fname, path;
3304 	if (splitNavigationPos(historyPath, fname, path)) {
3305 		lString32 curr_fname = getNavigationPath();
3306 		if (curr_fname != fname) {
3307 			CRLog::debug(
3308                     "navigateTo() : file name doesn't match: current=%s, new=%s",
3309 					LCSTR(curr_fname), LCSTR(fname));
3310 			if (!goLink(fname, false))
3311 				return false;
3312 		}
3313 	}
3314 	if (path.empty())
3315 		return false;
3316 	ldomXPointer bookmark = m_doc->createXPointer(path);
3317 	if (bookmark.isNull())
3318 		return false;
3319 	goToBookmark(bookmark);
3320     updateBookMarksRanges();
3321 	return true;
3322 }
3323 
3324 /// go back. returns true if navigation was successful
canGoBack()3325 bool LVDocView::canGoBack() {
3326     return _navigationHistory.backCount() > 0;
3327 }
3328 
3329 /// go forward. returns true if navigation was successful
canGoForward()3330 bool LVDocView::canGoForward() {
3331     return _navigationHistory.forwardCount() > 0;
3332 }
3333 
3334 /// go back. returns true if navigation was successful
goBack()3335 bool LVDocView::goBack() {
3336 	if (_navigationHistory.forwardCount() == 0 && savePosToNavigationHistory())
3337 		_navigationHistory.back();
3338 	lString32 s = _navigationHistory.back();
3339 	if (s.empty())
3340 		return false;
3341 	return navigateTo(s);
3342 }
3343 
3344 /// go forward. returns true if navigation was successful
goForward()3345 bool LVDocView::goForward() {
3346 	lString32 s = _navigationHistory.forward();
3347 	if (s.empty())
3348 		return false;
3349 	return navigateTo(s);
3350 }
3351 
3352 /// update selection ranges
updateSelections()3353 void LVDocView::updateSelections() {
3354     CHECK_RENDER("updateSelections()")
3355 	clearImageCache();
3356 	LVLock lock(getMutex());
3357 	ldomXRangeList ranges(m_doc->getSelections(), true);
3358     CRLog::trace("updateSelections() : selection count = %d", m_doc->getSelections().length());
3359 	ranges.getRanges(m_markRanges);
3360 	if (m_markRanges.length() > 0) {
3361 //		crtrace trace;
3362 //		trace << "LVDocView::updateSelections() - " << "selections: "
3363 //				<< m_doc->getSelections().length() << ", ranges: "
3364 //				<< ranges.length() << ", marked ranges: "
3365 //				<< m_markRanges.length() << " ";
3366 //		for (int i = 0; i < m_markRanges.length(); i++) {
3367 //			ldomMarkedRange * item = m_markRanges[i];
3368 //			trace << "(" << item->start.x << "," << item->start.y << "--"
3369 //					<< item->end.x << "," << item->end.y << " #" << item->flags
3370 //					<< ") ";
3371 //		}
3372 	}
3373 }
3374 
updateBookMarksRanges()3375 void LVDocView::updateBookMarksRanges()
3376 {
3377     checkRender();
3378     LVLock lock(getMutex());
3379     clearImageCache();
3380 
3381     ldomXRangeList ranges;
3382     CRFileHistRecord * rec = m_highlightBookmarks ? getCurrentFileHistRecord() : NULL;
3383     if (rec) {
3384         LVPtrVector<CRBookmark> &bookmarks = rec->getBookmarks();
3385         for (int i = 0; i < bookmarks.length(); i++) {
3386             CRBookmark * bmk = bookmarks[i];
3387             int t = bmk->getType();
3388             if (t != bmkt_lastpos) {
3389                 ldomXPointer p = m_doc->createXPointer(bmk->getStartPos());
3390                 if (p.isNull())
3391                     continue;
3392                 lvPoint pt = p.toPoint();
3393                 if (pt.y < 0)
3394                     continue;
3395                 ldomXPointer ep = (t == bmkt_pos) ? p : m_doc->createXPointer(bmk->getEndPos());
3396                 if (ep.isNull())
3397                     continue;
3398                 lvPoint ept = ep.toPoint();
3399                 if (ept.y < 0)
3400                     continue;
3401                 ldomXRange *n_range = new ldomXRange(p, ep);
3402                 if (!n_range->isNull()) {
3403                     int flags = 1;
3404                     if (t == bmkt_pos)
3405                         flags = 2;
3406                     if (t == bmkt_comment)
3407                         flags = 4;
3408                     if (t == bmkt_correction)
3409                         flags = 8;
3410                     n_range->setFlags(flags);
3411                     ranges.add(n_range);
3412                 } else
3413                     delete n_range;
3414             }
3415         }
3416     }
3417     ranges.getRanges(m_bmkRanges);
3418 #if 0
3419 
3420     m_bookmarksPercents.clear();
3421     if (m_highlightBookmarks) {
3422         CRFileHistRecord * rec = getCurrentFileHistRecord();
3423         if (rec) {
3424             LVPtrVector < CRBookmark > &bookmarks = rec->getBookmarks();
3425 
3426             m_bookmarksPercents.reserve(m_pages.length());
3427             for (int i = 0; i < bookmarks.length(); i++) {
3428                 CRBookmark * bmk = bookmarks[i];
3429                 if (bmk->getType() != bmkt_comment && bmk->getType() != bmkt_correction)
3430                     continue;
3431                 lString32 pos = bmk->getStartPos();
3432                 ldomXPointer p = m_doc->createXPointer(pos);
3433                 if (p.isNull())
3434                     continue;
3435                 lvPoint pt = p.toPoint();
3436                 if (pt.y < 0)
3437                     continue;
3438                 ldomXPointer ep = m_doc->createXPointer(bmk->getEndPos());
3439                 if (ep.isNull())
3440                     continue;
3441                 lvPoint ept = ep.toPoint();
3442                 if (ept.y < 0)
3443                     continue;
3444                 insertBookmarkPercentInfo(m_pages.FindNearestPage(pt.y, 0),
3445                                           ept.y, bmk->getPercent());
3446             }
3447         }
3448     }
3449 
3450     ldomXRangeList ranges;
3451     CRFileHistRecord * rec = m_bookmarksPercents.length() ? getCurrentFileHistRecord() : NULL;
3452     if (!rec) {
3453         m_bmkRanges.clear();
3454         return;
3455     }
3456     int page_index = getCurPage();
3457     if (page_index >= 0 && page_index < m_bookmarksPercents.length()) {
3458         LVPtrVector < CRBookmark > &bookmarks = rec->getBookmarks();
3459         LVRef < ldomXRange > page = getPageDocumentRange();
3460         LVBookMarkPercentInfo *bmi = m_bookmarksPercents[page_index];
3461         for (int i = 0; bmi != NULL && i < bmi->length(); i++) {
3462             for (int j = 0; j < bookmarks.length(); j++) {
3463                 CRBookmark * bmk = bookmarks[j];
3464                 if ((bmk->getType() != bmkt_comment && bmk->getType() != bmkt_correction) ||
3465                     bmk->getPercent() != bmi->get(i))
3466                     continue;
3467                 lString32 epos = bmk->getEndPos();
3468                 ldomXPointer ep = m_doc->createXPointer(epos);
3469                 if (!ep.isNull()) {
3470                     lString32 spos = bmk->getStartPos();
3471                     ldomXPointer sp = m_doc->createXPointer(spos);
3472                     if (!sp.isNull()) {
3473                         ldomXRange bmk_range(sp, ep);
3474 
3475                         ldomXRange *n_range = new ldomXRange(*page, bmk_range);
3476                         if (!n_range->isNull())
3477                             ranges.add(n_range);
3478                         else
3479                             delete n_range;
3480                     }
3481                 }
3482             }
3483         }
3484     }
3485     ranges.getRanges(m_bmkRanges);
3486 #endif
3487 }
3488 
3489 /// set view mode (pages/scroll)
setViewMode(LVDocViewMode view_mode,int visiblePageCount)3490 void LVDocView::setViewMode(LVDocViewMode view_mode, int visiblePageCount) {
3491     //CRLog::trace("setViewMode(%d, %d) currMode=%d currPages=%d", (int)view_mode, visiblePageCount, m_view_mode, m_pagesVisible);
3492 	if (m_view_mode == view_mode && (visiblePageCount == m_pagesVisible
3493 			|| visiblePageCount < 1))
3494 		return;
3495 	clearImageCache();
3496 	LVLock lock(getMutex());
3497 	m_view_mode = view_mode;
3498 	m_props->setInt(PROP_PAGE_VIEW_MODE, m_view_mode == DVM_PAGES ? 1 : 0);
3499     if (visiblePageCount == 1 || visiblePageCount == 2) {
3500 		m_pagesVisible = visiblePageCount;
3501         m_props->setInt(PROP_LANDSCAPE_PAGES, m_pagesVisible);
3502     }
3503     updateLayout();
3504     REQUEST_RENDER("setViewMode")
3505     _posIsSet = false;
3506 
3507 //	goToBookmark( _posBookmark);
3508 //        updateBookMarksRanges();
3509 }
3510 
3511 /// get view mode (pages/scroll)
getViewMode()3512 LVDocViewMode LVDocView::getViewMode() {
3513 	return m_view_mode;
3514 }
3515 
3516 /// toggle pages/scroll view mode
toggleViewMode()3517 void LVDocView::toggleViewMode() {
3518 	if (m_view_mode == DVM_SCROLL)
3519 		setViewMode( DVM_PAGES);
3520 	else
3521 		setViewMode( DVM_SCROLL);
3522 
3523 }
3524 
3525 /// returns current pages visible setting value
getPagesVisibleSetting()3526 int LVDocView::getPagesVisibleSetting() {
3527     if (m_view_mode == DVM_PAGES && m_pagesVisible == 2)
3528         return 2;
3529     return 1;
3530 }
3531 
getVisiblePageCount()3532 int LVDocView::getVisiblePageCount() {
3533     if (m_view_mode == DVM_SCROLL || m_pagesVisible == 1)
3534         return 1;
3535     if (m_pagesVisibleOverride > 0)
3536         return m_pagesVisibleOverride;
3537     return (m_dx < m_font_size * MIN_EM_PER_PAGE || m_dx * 5 < m_dy * 6)
3538             ? 1 : m_pagesVisible;
3539 }
3540 
overrideVisiblePageCount(int n)3541 void LVDocView::overrideVisiblePageCount(int n) {
3542     clearImageCache();
3543     LVLock lock(getMutex());
3544     int newCount = n > 0 ? ((n == 2) ? 2 : 1) : 0;
3545     if (m_pagesVisibleOverride == newCount)
3546         return;
3547     m_pagesVisibleOverride = newCount;
3548     updateLayout();
3549     REQUEST_RENDER("setVisiblePageCount")
3550     _posIsSet = false;
3551 }
3552 
3553 /// set window visible page count (1 or 2)
setVisiblePageCount(int n)3554 void LVDocView::setVisiblePageCount(int n) {
3555     //CRLog::trace("setVisiblePageCount(%d) currPages=%d", n, m_pagesVisible);
3556     clearImageCache();
3557 	LVLock lock(getMutex());
3558     int newCount = (n == 2) ? 2 : 1;
3559     if (m_pagesVisible == newCount)
3560         return;
3561     m_pagesVisible = newCount;
3562 	updateLayout();
3563     REQUEST_RENDER("setVisiblePageCount")
3564     _posIsSet = false;
3565 }
3566 
3567 #if USE_LIMITED_FONT_SIZES_SET
findBestFit(LVArray<int> & v,int n,bool rollCyclic=false)3568 static int findBestFit(LVArray<int> & v, int n, bool rollCyclic = false) {
3569 	int bestsz = -1;
3570 	int bestfit = -1;
3571 	if (rollCyclic) {
3572 		if (n < v[0])
3573 			return v[v.length() - 1];
3574 		if (n > v[v.length() - 1])
3575 			return v[0];
3576 	}
3577 	for (int i = 0; i < v.length(); i++) {
3578 		int delta = v[i] - n;
3579 		if (delta < 0)
3580 			delta = -delta;
3581 		if (bestfit == -1 || bestfit > delta) {
3582 			bestfit = delta;
3583 			bestsz = v[i];
3584 		}
3585 	}
3586 	if (bestsz < 0)
3587 		bestsz = n;
3588 	return bestsz;
3589 }
3590 #endif
3591 
setDefaultInterlineSpace(int percent)3592 void LVDocView::setDefaultInterlineSpace(int percent) {
3593     LVLock lock(getMutex());
3594     REQUEST_RENDER("setDefaultInterlineSpace")
3595     m_def_interline_space = percent; // not used
3596     if (m_doc) {
3597         if (percent == 100) // (avoid any rounding issue)
3598             m_doc->setInterlineScaleFactor(INTERLINE_SCALE_FACTOR_NO_SCALE);
3599         else
3600             m_doc->setInterlineScaleFactor(INTERLINE_SCALE_FACTOR_NO_SCALE * percent / 100);
3601     }
3602     _posIsSet = false;
3603 //	goToBookmark( _posBookmark);
3604 //        updateBookMarksRanges();
3605 }
3606 
3607 /// sets new status bar font size
setStatusFontSize(int newSize)3608 void LVDocView::setStatusFontSize(int newSize) {
3609 	LVLock lock(getMutex());
3610 	int oldSize = m_status_font_size;
3611 	m_status_font_size = newSize;
3612 	if (oldSize != newSize) {
3613 		propsGetCurrent()->setInt(PROP_STATUS_FONT_SIZE, m_status_font_size);
3614         REQUEST_RENDER("setStatusFontSize")
3615 	}
3616 	//goToBookmark(_posBookmark);
3617 }
3618 
scaleFontSizeForDPI(int fontSize)3619 int LVDocView::scaleFontSizeForDPI(int fontSize) {
3620     if (gRenderScaleFontWithDPI) {
3621         fontSize = scaleForRenderDPI(fontSize);
3622 #if USE_LIMITED_FONT_SIZES_SET
3623         fontSize = findBestFit(m_font_sizes, fontSize);
3624 #else
3625         if (fontSize < m_min_font_size)
3626             fontSize = m_min_font_size;
3627         else if (fontSize > m_max_font_size)
3628             fontSize = m_max_font_size;
3629 #endif
3630     }
3631     return fontSize;
3632 }
3633 
setFontSize(int newSize)3634 void LVDocView::setFontSize(int newSize) {
3635     LVLock lock(getMutex());
3636 
3637     // We don't scale m_requested_font_size itself, so font size and gRenderDPI
3638     // can be changed independantly.
3639     int oldSize = m_requested_font_size;
3640     if (oldSize != newSize) {
3641 #if USE_LIMITED_FONT_SIZES_SET
3642         m_requested_font_size = findBestFit(m_font_sizes, newSize);
3643 #else
3644         if (newSize < m_min_font_size)
3645             m_requested_font_size = m_min_font_size;
3646         else if (newSize > m_max_font_size)
3647             m_requested_font_size = m_max_font_size;
3648         else
3649             m_requested_font_size = newSize;
3650 #endif
3651         propsGetCurrent()->setInt(PROP_FONT_SIZE, m_requested_font_size);
3652         m_font_size = scaleFontSizeForDPI(m_requested_font_size);
3653         gRootFontSize = m_font_size; // stored as global (for 'rem' css unit)
3654         CRLog::debug("New requested font size: %d (asked: %d)", m_requested_font_size, newSize);
3655         REQUEST_RENDER("setFontSize")
3656     }
3657     //goToBookmark(_posBookmark);
3658 }
3659 
setDefaultFontFace(const lString8 & newFace)3660 void LVDocView::setDefaultFontFace(const lString8 & newFace) {
3661 	m_defaultFontFace = newFace;
3662     REQUEST_RENDER("setDefaulFontFace")
3663 }
3664 
setStatusFontFace(const lString8 & newFace)3665 void LVDocView::setStatusFontFace(const lString8 & newFace) {
3666 	m_statusFontFace = newFace;
3667     REQUEST_RENDER("setStatusFontFace")
3668 }
3669 
3670 #if USE_LIMITED_FONT_SIZES_SET
3671 /// sets posible base font sizes (for ZoomFont feature)
setFontSizes(LVArray<int> & sizes,bool cyclic)3672 void LVDocView::setFontSizes(LVArray<int> & sizes, bool cyclic) {
3673 	m_font_sizes = sizes;
3674 	m_font_sizes_cyclic = cyclic;
3675 }
3676 #endif
3677 
3678 #if !USE_LIMITED_FONT_SIZES_SET
setMinFontSize(int size)3679 void LVDocView::setMinFontSize( int size ) {
3680 	m_min_font_size = size;
3681 }
3682 
setMaxFontSize(int size)3683 void LVDocView::setMaxFontSize( int size ) {
3684 	m_max_font_size = size;
3685 }
3686 #endif
3687 
ZoomFont(int delta)3688 void LVDocView::ZoomFont(int delta) {
3689 	if (m_font.isNull())
3690 		return;
3691 #if 1
3692 #if USE_LIMITED_FONT_SIZES_SET
3693 	int sz = m_requested_font_size;
3694 	for (int i = 0; i < 15; i++) {
3695 		sz += delta;
3696 		int nsz = findBestFit(m_font_sizes, sz, m_font_sizes_cyclic);
3697 		if (nsz != m_requested_font_size) {
3698 			setFontSize(nsz);
3699 			return;
3700 		}
3701 		if (sz < 12)
3702 			break;
3703 	}
3704 #else
3705 	setFontSize(m_requested_font_size + delta);
3706 #endif
3707 #else
3708 	LVFontRef nfnt;
3709 	int sz = m_font->getHeight();
3710 	for (int i=0; i<15; i++)
3711 	{
3712 		sz += delta;
3713 		nfnt = fontMan->GetFont( sz, 400, false, DEFAULT_FONT_FAMILY, lString8(DEFAULT_FONT_NAME) );
3714 		if ( !nfnt.isNull() && nfnt->getHeight() != m_font->getHeight() )
3715 		{
3716 			// found!
3717 			//ldomXPointer bm = getBookmark();
3718 			m_requested_font_size = nfnt->getHeight();
3719 			Render();
3720 			goToBookmark(_posBookmark);
3721 			return;
3722 		}
3723 		if (sz<12)
3724 		break;
3725 	}
3726 #endif
3727 }
3728 
3729 /// sets current bookmark
setBookmark(ldomXPointer bm)3730 void LVDocView::setBookmark(ldomXPointer bm) {
3731 	_posBookmark = bm;
3732 }
3733 
3734 /// get view height
GetHeight()3735 int LVDocView::GetHeight() {
3736 #if CR_INTERNAL_PAGE_ORIENTATION==1
3737 	return (m_rotateAngle & 1) ? m_dx : m_dy;
3738 #else
3739 	return m_dy;
3740 #endif
3741 }
3742 
3743 /// get view width
GetWidth()3744 int LVDocView::GetWidth() {
3745 #if CR_INTERNAL_PAGE_ORIENTATION==1
3746 	return (m_rotateAngle & 1) ? m_dy : m_dx;
3747 #else
3748 	return m_dx;
3749 #endif
3750 }
3751 
3752 #if CR_INTERNAL_PAGE_ORIENTATION==1
3753 /// sets rotate angle
SetRotateAngle(cr_rotate_angle_t angle)3754 void LVDocView::SetRotateAngle( cr_rotate_angle_t angle )
3755 {
3756 	if ( m_rotateAngle==angle )
3757 	return;
3758 	m_props->setInt( PROP_ROTATE_ANGLE, ((int)angle) & 3 );
3759 	clearImageCache();
3760 	LVLock lock(getMutex());
3761 	if ( (m_rotateAngle & 1) == (angle & 1) ) {
3762 		m_rotateAngle = angle;
3763 		return;
3764 	}
3765 	m_rotateAngle = angle;
3766 	int ndx = (angle&1) ? m_dx : m_dy;
3767 	int ndy = (angle&1) ? m_dy : m_dx;
3768 	Resize( ndx, ndy );
3769 }
3770 #endif
3771 
Resize(int dx,int dy)3772 void LVDocView::Resize(int dx, int dy) {
3773 	//LVCHECKPOINT("Resize");
3774 	CRLog::trace("LVDocView:Resize(%dx%d)", dx, dy);
3775 	if (dx < SCREEN_SIZE_MIN)
3776 		dx = SCREEN_SIZE_MIN;
3777 	else if (dx > SCREEN_SIZE_MAX)
3778 		dx = SCREEN_SIZE_MAX;
3779 	if (dy < SCREEN_SIZE_MIN)
3780 		dy = SCREEN_SIZE_MIN;
3781 	else if (dy > SCREEN_SIZE_MAX)
3782 		dy = SCREEN_SIZE_MAX;
3783 #if CR_INTERNAL_PAGE_ORIENTATION==1
3784 	if ( m_rotateAngle==CR_ROTATE_ANGLE_90 || m_rotateAngle==CR_ROTATE_ANGLE_270 ) {
3785 		CRLog::trace("Screen is rotated, swapping dimensions");
3786 		int tmp = dx;
3787 		dx = dy;
3788 		dy = tmp;
3789 	}
3790 #endif
3791 
3792 	if (dx == m_dx && dy == m_dy) {
3793 		CRLog::trace("Size is not changed: %dx%d", dx, dy);
3794 		return;
3795 	}
3796 
3797 	clearImageCache();
3798 	//m_drawbuf.Resize(dx, dy);
3799 	if (m_doc) {
3800 		//ldomXPointer bm = getBookmark();
3801 		if (dx != m_dx || dy != m_dy || m_view_mode != DVM_SCROLL
3802 				|| !m_is_rendered) {
3803 			m_dx = dx;
3804 			m_dy = dy;
3805 			CRLog::trace("LVDocView:Resize() :  new size: %dx%d", dx, dy);
3806 			updateLayout();
3807             REQUEST_RENDER("resize")
3808 		}
3809         _posIsSet = false;
3810 //		goToBookmark( _posBookmark);
3811 //                updateBookMarksRanges();
3812 	}
3813 	m_dx = dx;
3814 	m_dy = dy;
3815 }
3816 
3817 #define XS_IMPLEMENT_SCHEME 1
3818 #include "../include/fb2def.h"
3819 
3820 #if 0
3821 void SaveBase64Objects( ldomNode * node )
3822 {
3823 	if ( !node->isElement() || node->getNodeId()!=el_binary )
3824 	return;
3825 	lString32 name = node->getAttributeValue(attr_id);
3826 	if ( name.empty() )
3827 	return;
3828 	fprintf( stderr, "opening base64 stream...\n" );
3829 	LVStreamRef in = node->createBase64Stream();
3830 	if ( in.isNull() )
3831 	return;
3832 	fprintf( stderr, "base64 stream opened: %d bytes\n", (int)in->GetSize() );
3833 	fprintf( stderr, "opening out stream...\n" );
3834 	LVStreamRef outstream = LVOpenFileStream( name.c_str(), LVOM_WRITE );
3835 	if (outstream.isNull())
3836 	return;
3837 	//outstream->Write( "test", 4, NULL );
3838 	fprintf( stderr, "streams opened, copying...\n" );
3839 	/*
3840 	 lUInt8 dbuf[128000];
3841 	 lvsize_t bytesRead = 0;
3842 	 if ( in->Read( dbuf, 128000, &bytesRead )==LVERR_OK )
3843 	 {
3844 	 fprintf(stderr, "Read %d bytes, writing...\n", (int) bytesRead );
3845 	 //outstream->Write( "test2", 5, NULL );
3846 	 //outstream->Write( "test3", 5, NULL );
3847 	 outstream->Write( dbuf, 100, NULL );
3848 	 outstream->Write( dbuf, bytesRead, NULL );
3849 	 //outstream->Write( "test4", 5, NULL );
3850 	 }
3851 	 */
3852 	LVPumpStream( outstream, in );
3853 	fprintf(stderr, "...\n");
3854 }
3855 #endif
3856 
3857 /// returns pointer to bookmark/last position containter of currently opened file
getCurrentFileHistRecord()3858 CRFileHistRecord * LVDocView::getCurrentFileHistRecord() {
3859 	if (m_filename.empty())
3860 		return NULL;
3861 	//CRLog::trace("LVDocView::getCurrentFileHistRecord()");
3862 	//CRLog::trace("get title, authors, series");
3863 	lString32 title = getTitle();
3864 	lString32 authors = getAuthors();
3865 	lString32 series = getSeries();
3866 	//CRLog::trace("get bookmark");
3867 	ldomXPointer bmk = getBookmark();
3868     lString32 fn = m_filename;
3869 #ifdef ORIGINAL_FILENAME_PATCH
3870     if ( !m_originalFilename.empty() )
3871         fn = m_originalFilename;
3872 #endif
3873     //CRLog::debug("m_hist.savePosition(%s, %d)", LCSTR(fn), m_filesize);
3874     CRFileHistRecord * res = m_hist.savePosition(fn, m_filesize, title,
3875 			authors, series, bmk);
3876 	//CRLog::trace("savePosition() returned");
3877 	return res;
3878 }
3879 
3880 /// save last file position
savePosition()3881 void LVDocView::savePosition() {
3882 	getCurrentFileHistRecord();
3883 }
3884 
3885 /// restore last file position
restorePosition()3886 void LVDocView::restorePosition() {
3887 	//CRLog::trace("LVDocView::restorePosition()");
3888 	if (m_filename.empty())
3889 		return;
3890 	LVLock lock(getMutex());
3891 	//checkRender();
3892     lString32 fn = m_filename;
3893 #ifdef ORIGINAL_FILENAME_PATCH
3894     if ( !m_originalFilename.empty() )
3895         fn = m_originalFilename;
3896 #endif
3897 //    CRLog::debug("m_hist.restorePosition(%s, %d)", LCSTR(fn),
3898 //			m_filesize);
3899     ldomXPointer pos = m_hist.restorePosition(m_doc, fn, m_filesize);
3900 	if (!pos.isNull()) {
3901 		//goToBookmark( pos );
3902 		CRLog::info("LVDocView::restorePosition() - last position is found");
3903 		_posBookmark = pos; //getBookmark();
3904         updateBookMarksRanges();
3905 		_posIsSet = false;
3906 	} else {
3907 		CRLog::info(
3908 				"LVDocView::restorePosition() - last position not found for file %s, size %d",
3909 				UnicodeToUtf8(m_filename).c_str(), (int) m_filesize);
3910 	}
3911 }
3912 
FileToArcProps(CRPropRef props)3913 static void FileToArcProps(CRPropRef props) {
3914 	lString32 s = props->getStringDef(DOC_PROP_FILE_NAME);
3915 	if (!s.empty())
3916 		props->setString(DOC_PROP_ARC_NAME, s);
3917 	s = props->getStringDef(DOC_PROP_FILE_PATH);
3918 	if (!s.empty())
3919 		props->setString(DOC_PROP_ARC_PATH, s);
3920 	s = props->getStringDef(DOC_PROP_FILE_SIZE);
3921 	if (!s.empty())
3922 		props->setString(DOC_PROP_ARC_SIZE, s);
3923     props->setString(DOC_PROP_FILE_NAME, lString32::empty_str);
3924     props->setString(DOC_PROP_FILE_PATH, lString32::empty_str);
3925     props->setString(DOC_PROP_FILE_SIZE, lString32::empty_str);
3926 	props->setHex(DOC_PROP_FILE_CRC32, 0);
3927 }
3928 
needToConvertBookmarks(CRFileHistRecord * historyRecord,lUInt32 domVersionRequested)3929 static bool needToConvertBookmarks(CRFileHistRecord* historyRecord, lUInt32 domVersionRequested)
3930 {
3931     bool convertBookmarks = false;
3932     if (historyRecord && historyRecord->getBookmarks().length() > 1
3933         && domVersionRequested >= DOM_VERSION_WITH_NORMALIZED_XPOINTERS
3934         && historyRecord->getDOMversion() < DOM_VERSION_WITH_NORMALIZED_XPOINTERS) {
3935             convertBookmarks = true;
3936     }
3937     return convertBookmarks;
3938 }
3939 
3940 /// load document from file
LoadDocument(const lChar32 * fname,bool metadataOnly)3941 bool LVDocView::LoadDocument(const lChar32 * fname, bool metadataOnly) {
3942 	if (!fname || !fname[0])
3943 		return false;
3944 
3945 	Clear();
3946 
3947     CRLog::debug("LoadDocument(%s) textMode=%s", LCSTR(lString32(fname)), getTextFormatOptions()==txt_format_pre ? "pre" : "autoformat");
3948 
3949 	// split file path and name
3950 	lString32 filename32(fname);
3951 
3952 	lString32 arcPathName;
3953 	lString32 arcItemPathName;
3954 	bool isArchiveFile = LVSplitArcName(filename32, arcPathName,
3955 			arcItemPathName);
3956 
3957 	int savedRenderFlags = m_props->getIntDef(PROP_RENDER_BLOCK_RENDERING_FLAGS, BLOCK_RENDERING_FLAGS_LEGACY);
3958 	if (isArchiveFile) {
3959 		// load from archive, using @/ separated arhive/file pathname
3960 		CRLog::info("Loading document %s from archive %s", LCSTR(
3961 				arcItemPathName), LCSTR(arcPathName));
3962 		LVStreamRef stream = LVOpenFileStream(arcPathName.c_str(), LVOM_READ);
3963 		int arcsize = 0;
3964 		if (stream.isNull()) {
3965 			CRLog::error("Cannot open archive file %s", LCSTR(arcPathName));
3966 			return false;
3967 		}
3968 		arcsize = (int) stream->GetSize();
3969 		m_container = LVOpenArchieve(stream);
3970 		if (m_container.isNull()) {
3971 			CRLog::error("Cannot read archive contents from %s", LCSTR(
3972 					arcPathName));
3973 			return false;
3974 		}
3975 		stream = m_container->OpenStream(arcItemPathName.c_str(), LVOM_READ);
3976 		if (stream.isNull()) {
3977 			CRLog::error("Cannot open archive file item stream %s", LCSTR(
3978 					filename32));
3979 			return false;
3980 		}
3981 
3982 		lString32 fn = LVExtractFilename(arcPathName);
3983 		lString32 dir = LVExtractPath(arcPathName);
3984 
3985 		m_doc_props->setString(DOC_PROP_ARC_NAME, fn);
3986 		m_doc_props->setString(DOC_PROP_ARC_PATH, dir);
3987 		m_doc_props->setString(DOC_PROP_ARC_SIZE, lString32::itoa(arcsize));
3988 		m_doc_props->setString(DOC_PROP_FILE_SIZE, lString32::itoa(
3989 				(int) stream->GetSize()));
3990 		m_doc_props->setString(DOC_PROP_FILE_NAME, arcItemPathName);
3991 		m_doc_props->setHex(DOC_PROP_FILE_CRC32, stream->getcrc32());
3992 		CRFileHistRecord* record = m_hist.getRecord( filename32, stream->GetSize() );
3993 		lUInt32 newDOMVersion;
3994 		lUInt32 domVersionRequested = m_props->getIntDef(PROP_REQUESTED_DOM_VERSION, gDOMVersionCurrent);
3995 		bool convertBookmarks = needToConvertBookmarks(record, domVersionRequested) && !metadataOnly;
3996 		if(convertBookmarks) {
3997 			newDOMVersion = domVersionRequested;
3998 			m_props->setInt(PROP_REQUESTED_DOM_VERSION, record->getDOMversion());
3999 			m_props->setInt(PROP_RENDER_BLOCK_RENDERING_FLAGS, BLOCK_RENDERING_FLAGS_LEGACY);
4000 		}
4001 		// loading document
4002 		if (loadDocumentInt(stream, metadataOnly)) {
4003 			m_filename = lString32(fname);
4004 			m_stream.Clear();
4005 			if(convertBookmarks) {
4006 				record->convertBookmarks(m_doc, newDOMVersion);
4007 				m_props->setInt(PROP_REQUESTED_DOM_VERSION, newDOMVersion);
4008 				m_props->setInt(PROP_RENDER_BLOCK_RENDERING_FLAGS, savedRenderFlags);
4009 				//FIXME: need to reload file after this
4010 			}
4011 			return true;
4012 		}
4013 		m_stream.Clear();
4014 		return false;
4015 	}
4016 
4017 	lString32 fn = LVExtractFilename(filename32);
4018 	lString32 dir = LVExtractPath(filename32);
4019 
4020 	CRLog::info("Loading document %s : fn=%s, dir=%s", LCSTR(filename32),
4021 			LCSTR(fn), LCSTR(dir));
4022 #if 0
4023 	int i;
4024 	int last_slash = -1;
4025 	lChar32 slash_char = 0;
4026 	for ( i=0; fname[i]; i++ ) {
4027 		if ( fname[i]=='\\' || fname[i]=='/' ) {
4028 			last_slash = i;
4029 			slash_char = fname[i];
4030 		}
4031 	}
4032 	lString32 dir;
4033 	if ( last_slash==-1 )
4034         dir = ".";
4035 	else if ( last_slash == 0 )
4036         dir << slash_char;
4037 	else
4038         dir = lString32( fname, last_slash );
4039 	lString32 fn( fname + last_slash + 1 );
4040 #endif
4041 
4042 	m_doc_props->setString(DOC_PROP_FILE_PATH, dir);
4043 	m_container = LVOpenDirectory(dir.c_str());
4044 	if (m_container.isNull())
4045 		return false;
4046 	LVStreamRef stream = m_container->OpenStream(fn.c_str(), LVOM_READ);
4047 	if (!stream)
4048 		return false;
4049 	m_doc_props->setString(DOC_PROP_FILE_NAME, fn);
4050 	m_doc_props->setString(DOC_PROP_FILE_SIZE, lString32::itoa(
4051 			(int) stream->GetSize()));
4052 	m_doc_props->setHex(DOC_PROP_FILE_CRC32, stream->getcrc32());
4053 
4054 	CRFileHistRecord* record = m_hist.getRecord( filename32, stream->GetSize() );
4055 	int newDOMVersion;
4056 	lUInt32 domVersionRequested = m_props->getIntDef(PROP_REQUESTED_DOM_VERSION, gDOMVersionCurrent);
4057 	bool convertBookmarks = needToConvertBookmarks(record, domVersionRequested) && !metadataOnly;
4058 	if(convertBookmarks) {
4059 		newDOMVersion = domVersionRequested;
4060 		m_props->setInt(PROP_REQUESTED_DOM_VERSION, record->getDOMversion());
4061 		m_props->setInt(PROP_RENDER_BLOCK_RENDERING_FLAGS, BLOCK_RENDERING_FLAGS_LEGACY);
4062 	}
4063 
4064 	if (loadDocumentInt(stream, metadataOnly)) {
4065 		m_filename = lString32(fname);
4066 		m_stream.Clear();
4067 		if(convertBookmarks) {
4068 			record->convertBookmarks(m_doc, newDOMVersion);
4069 			m_props->setInt(PROP_REQUESTED_DOM_VERSION, newDOMVersion);
4070 			m_props->setInt(PROP_RENDER_BLOCK_RENDERING_FLAGS, savedRenderFlags);
4071 			//FIXME: need to reload file after this
4072 		}
4073 #define DUMP_OPENED_DOCUMENT_SENTENCES 0 // debug XPointer navigation
4074 #if DUMP_OPENED_DOCUMENT_SENTENCES==1
4075         LVStreamRef out = LVOpenFileStream("/tmp/sentences.txt", LVOM_WRITE);
4076         if ( !out.isNull() ) {
4077             checkRender();
4078             {
4079                 ldomXPointerEx ptr( m_doc->getRootNode(), m_doc->getRootNode()->getChildCount());
4080                 *out << "FORWARD ORDER:\n\n";
4081                 //ptr.nextVisibleText();
4082                 ptr.prevVisibleWordEnd();
4083                 if ( ptr.thisSentenceStart() ) {
4084                     while ( 1 ) {
4085                         ldomXPointerEx ptr2(ptr);
4086                         ptr2.thisSentenceEnd();
4087                         ldomXRange range(ptr, ptr2);
4088                         lString32 str = range.getRangeText();
4089                         *out << ">sentence: " << UnicodeToUtf8(str) << "\n";
4090                         if ( !ptr.nextSentenceStart() )
4091                             break;
4092                     }
4093                 }
4094             }
4095             {
4096                 ldomXPointerEx ptr( m_doc->getRootNode(), 1);
4097                 *out << "\n\nBACKWARD ORDER:\n\n";
4098                 while ( ptr.lastChild() )
4099                     ;// do nothing
4100                 if ( ptr.thisSentenceStart() ) {
4101                     while ( 1 ) {
4102                         ldomXPointerEx ptr2(ptr);
4103                         ptr2.thisSentenceEnd();
4104                         ldomXRange range(ptr, ptr2);
4105                         lString32 str = range.getRangeText();
4106                         *out << "<sentence: " << UnicodeToUtf8(str) << "\n";
4107                         if ( !ptr.prevSentenceStart() )
4108                             break;
4109                     }
4110                 }
4111             }
4112         }
4113 #endif
4114 
4115 		return true;
4116 	}
4117 	m_stream.Clear();
4118 	return false;
4119 }
4120 
LoadDocument(LVStreamRef stream,const lChar32 * contentPath,bool metadataOnly)4121 bool LVDocView::LoadDocument( LVStreamRef stream, const lChar32 * contentPath, bool metadataOnly )
4122 {
4123 	if (stream.isNull() || !contentPath || !contentPath[0])
4124 		return false;
4125 
4126 	Clear();
4127 
4128 	CRLog::debug("LoadDocument(%s) textMode=%s", LCSTR(lString32(contentPath)), getTextFormatOptions()==txt_format_pre ? "pre" : "autoformat");
4129 
4130 	// split file path and name
4131 	lString32 contentPath16(contentPath);
4132 
4133 	lString32 fn = LVExtractFilename(contentPath16);
4134 	lString32 dir = LVExtractPath(contentPath16);
4135 
4136 	CRLog::info("Loading document %s : fn=%s, dir=%s", LCSTR(contentPath16),
4137 				LCSTR(fn), LCSTR(dir));
4138 
4139 	m_doc_props->setString(DOC_PROP_FILE_PATH, dir);
4140 	m_doc_props->setString(DOC_PROP_FILE_NAME, fn);
4141 	m_doc_props->setString(DOC_PROP_FILE_SIZE, lString32::itoa(
4142 			(int) stream->GetSize()));
4143 	m_doc_props->setHex(DOC_PROP_FILE_CRC32, stream->getcrc32());
4144 
4145 	CRFileHistRecord* record = m_hist.getRecord( contentPath16, stream->GetSize() );
4146 	int newDOMVersion;
4147 	lUInt32 domVersionRequested = m_props->getIntDef(PROP_REQUESTED_DOM_VERSION, gDOMVersionCurrent);
4148 	int savedRenderFlags = m_props->getIntDef(PROP_RENDER_BLOCK_RENDERING_FLAGS, BLOCK_RENDERING_FLAGS_LEGACY);
4149 	bool convertBookmarks = needToConvertBookmarks(record, domVersionRequested) && !metadataOnly;
4150 	if(convertBookmarks) {
4151 		newDOMVersion = domVersionRequested;
4152 		m_props->setInt(PROP_REQUESTED_DOM_VERSION, record->getDOMversion());
4153 		m_props->setInt(PROP_RENDER_BLOCK_RENDERING_FLAGS, BLOCK_RENDERING_FLAGS_LEGACY);
4154 	}
4155 
4156 	if (loadDocumentInt(stream, metadataOnly)) {
4157 		m_filename = lString32(contentPath);
4158 		if(convertBookmarks) {
4159 			record->convertBookmarks(m_doc, newDOMVersion);
4160 			m_props->setInt(PROP_REQUESTED_DOM_VERSION, newDOMVersion);
4161 			m_props->setInt(PROP_RENDER_BLOCK_RENDERING_FLAGS, savedRenderFlags);
4162 			//FIXME: need to reload file after this
4163 		}
4164 		return true;
4165 	}
4166 	m_stream.Clear();
4167 	return false;
4168 }
4169 
close()4170 void LVDocView::close() {
4171     if ( m_doc )
4172         m_doc->updateMap(m_callback); // show save cache file progress
4173     createDefaultDocument(lString32::empty_str, lString32::empty_str);
4174 }
4175 
4176 
4177 /// create empty document with specified message (to show errors)
createHtmlDocument(lString32 code)4178 void LVDocView::createHtmlDocument(lString32 code) {
4179     Clear();
4180     m_showCover = false;
4181     createEmptyDocument();
4182 
4183     //ldomDocumentWriter writer(m_doc);
4184     ldomDocumentWriterFilter writerFilter(m_doc, false,
4185             HTML_AUTOCLOSE_TABLE);
4186 
4187     _pos = 0;
4188     _page = 0;
4189 
4190 
4191     lString8 s = UnicodeToUtf8(lString32(U"\xFEFF<html><body>") + code + "</body>");
4192     setDocFormat( doc_format_html);
4193     LVStreamRef stream = LVCreateMemoryStream();
4194     stream->Write(s.c_str(), s.length(), NULL);
4195     stream->SetPos(0);
4196     LVHTMLParser parser(stream, &writerFilter);
4197     if (!parser.CheckFormat()) {
4198         // error - cannot parse
4199     } else {
4200         parser.Parse();
4201     }
4202     REQUEST_RENDER("resize")
4203 }
4204 
createDefaultDocument(lString32 title,lString32 message)4205 void LVDocView::createDefaultDocument(lString32 title, lString32 message) {
4206     Clear();
4207     m_showCover = false;
4208     createEmptyDocument();
4209 
4210     ldomDocumentWriter writer(m_doc);
4211     lString32Collection lines;
4212     lines.split(message, lString32("\n"));
4213 
4214     _pos = 0;
4215     _page = 0;
4216 
4217     // make fb2 document structure
4218     writer.OnTagOpen(NULL, U"?xml");
4219     writer.OnAttribute(NULL, U"version", U"1.0");
4220     writer.OnAttribute(NULL, U"encoding", U"utf-8");
4221     writer.OnEncoding(U"utf-8", NULL);
4222     writer.OnTagBody();
4223     writer.OnTagClose(NULL, U"?xml");
4224     writer.OnTagOpenNoAttr(NULL, U"FictionBook");
4225     // DESCRIPTION
4226     writer.OnTagOpenNoAttr(NULL, U"description");
4227     writer.OnTagOpenNoAttr(NULL, U"title-info");
4228     writer.OnTagOpenNoAttr(NULL, U"book-title");
4229     writer.OnTagOpenNoAttr(NULL, U"lang");
4230     writer.OnText(title.c_str(), title.length(), 0);
4231     writer.OnTagClose(NULL, U"book-title");
4232     writer.OnTagOpenNoAttr(NULL, U"title-info");
4233     writer.OnTagClose(NULL, U"description");
4234     // BODY
4235     writer.OnTagOpenNoAttr(NULL, U"body");
4236     //m_callback->OnTagOpen( NULL, U"section" );
4237     // process text
4238     if (title.length()) {
4239         writer.OnTagOpenNoAttr(NULL, U"title");
4240         writer.OnTagOpenNoAttr(NULL, U"p");
4241         writer.OnText(title.c_str(), title.length(), 0);
4242         writer.OnTagClose(NULL, U"p");
4243         writer.OnTagClose(NULL, U"title");
4244     }
4245     lString32Collection messageLines;
4246     messageLines.split(message, lString32("\n"));
4247     for (int i = 0; i < messageLines.length(); i++) {
4248         writer.OnTagOpenNoAttr(NULL, U"p");
4249         writer.OnText(messageLines[i].c_str(), messageLines[i].length(), 0);
4250         writer.OnTagClose(NULL, U"p");
4251     }
4252     //m_callback->OnTagClose( NULL, U"section" );
4253     writer.OnTagClose(NULL, U"body");
4254     writer.OnTagClose(NULL, U"FictionBook");
4255 
4256     // set stylesheet
4257     updateDocStyleSheet();
4258     //m_doc->getStyleSheet()->clear();
4259     //m_doc->getStyleSheet()->parse(m_stylesheet.c_str());
4260 
4261     m_doc_props->clear();
4262     m_doc->setProps(m_doc_props);
4263 
4264     m_doc_props->setString(DOC_PROP_TITLE, title);
4265 
4266     REQUEST_RENDER("resize")
4267 }
4268 
4269 /// load document from stream
loadDocumentInt(LVStreamRef stream,bool metadataOnly)4270 bool LVDocView::loadDocumentInt(LVStreamRef stream, bool metadataOnly) {
4271 
4272 
4273 	m_swapDone = false;
4274 
4275 	setRenderProps(0, 0); // to allow apply styles and rend method while loading
4276 
4277 	if (m_callback) {
4278 		m_callback->OnLoadFileStart(m_doc_props->getStringDef(
4279 				DOC_PROP_FILE_NAME, ""));
4280 	}
4281 	LVLock lock(getMutex());
4282 
4283 //    int pdbFormat = 0;
4284 //    LVStreamRef pdbStream = LVOpenPDBStream( stream, pdbFormat );
4285 //    if ( !pdbStream.isNull() ) {
4286 //        CRLog::info("PDB format detected, stream size=%d", (int)pdbStream->GetSize() );
4287 //        LVStreamRef out = LVOpenFileStream("/tmp/pdb.txt", LVOM_WRITE);
4288 //        if ( !out.isNull() )
4289 //            LVPumpStream(out.get(), pdbStream.get()); // DEBUG
4290 //        stream = pdbStream;
4291 //        //return false;
4292 //    }
4293 
4294 	{
4295 		clearImageCache();
4296 		m_filesize = stream->GetSize();
4297 		m_stream = stream;
4298 
4299 #if (USE_ZLIB==1)
4300 
4301         doc_format_t pdbFormat = doc_format_none;
4302         if ( DetectPDBFormat(m_stream, pdbFormat) ) {
4303             // PDB
4304             CRLog::info("PDB format detected");
4305             createEmptyDocument();
4306             m_doc->setProps( m_doc_props );
4307             setRenderProps( 0, 0 ); // to allow apply styles and rend method while loading
4308             setDocFormat( pdbFormat );
4309             if ( m_callback )
4310                 m_callback->OnLoadFileFormatDetected(pdbFormat);
4311             updateDocStyleSheet();
4312             doc_format_t contentFormat = doc_format_none;
4313             bool res = ImportPDBDocument( m_stream, m_doc, m_callback, this, contentFormat );
4314             if ( !res ) {
4315                 setDocFormat( doc_format_none );
4316                 createDefaultDocument( cs32("ERROR: Error reading PDB format"), cs32("Cannot open document") );
4317                 if ( m_callback ) {
4318                     m_callback->OnLoadFileError( cs32("Error reading PDB document") );
4319                 }
4320                 return false;
4321             } else {
4322                 setRenderProps( 0, 0 );
4323                 REQUEST_RENDER("loadDocument")
4324                 if ( m_callback ) {
4325                     m_callback->OnLoadFileEnd( );
4326                     //m_doc->compact();
4327                     m_doc->dumpStatistics();
4328                 }
4329                 return true;
4330             }
4331         }
4332 
4333 
4334 		if ( DetectEpubFormat( m_stream ) ) {
4335 			// EPUB
4336 			CRLog::info("EPUB format detected");
4337 			createEmptyDocument();
4338             m_doc->setProps( m_doc_props );
4339 			setRenderProps( 0, 0 ); // to allow apply styles and rend method while loading
4340 			setDocFormat( doc_format_epub );
4341 			if ( m_callback )
4342                 m_callback->OnLoadFileFormatDetected(doc_format_epub);
4343             updateDocStyleSheet();
4344             bool res = ImportEpubDocument( m_stream, m_doc, m_callback, this, metadataOnly );
4345 			if ( !res ) {
4346 				setDocFormat( doc_format_none );
4347                 createDefaultDocument( cs32("ERROR: Error reading EPUB format"), cs32("Cannot open document") );
4348 				if ( m_callback ) {
4349                     m_callback->OnLoadFileError( cs32("Error reading EPUB document") );
4350 				}
4351 				return false;
4352 			} else {
4353 				m_container = m_doc->getContainer();
4354 				m_doc_props = m_doc->getProps();
4355 				setRenderProps( 0, 0 );
4356                 REQUEST_RENDER("loadDocument")
4357 				if ( m_callback ) {
4358 					m_callback->OnLoadFileEnd( );
4359 					//m_doc->compact();
4360 					m_doc->dumpStatistics();
4361 				}
4362 				m_arc = m_doc->getContainer();
4363 
4364 #ifdef SAVE_COPY_OF_LOADED_DOCUMENT //def _DEBUG
4365         LVStreamRef ostream = LVOpenFileStream( "test_save_source.xml", LVOM_WRITE );
4366         m_doc->saveToStream( ostream, "utf-16" );
4367 #endif
4368 
4369 				return true;
4370 			}
4371 		}
4372 
4373         if( DetectFb3Format(m_stream) ) {
4374             CRLog::info("FB3 format detected");
4375             createEmptyDocument();
4376             m_doc->setProps( m_doc_props );
4377             setRenderProps( 0, 0 );
4378             setDocFormat( doc_format_fb3 );
4379             if ( m_callback )
4380                 m_callback->OnLoadFileFormatDetected(doc_format_fb3);
4381             updateDocStyleSheet();
4382             bool res = ImportFb3Document( m_stream, m_doc, m_callback, this );
4383             if ( !res ) {
4384                 setDocFormat( doc_format_none );
4385                 createDefaultDocument( cs32("ERROR: Error reading FB3 format"), cs32("Cannot open document") );
4386                 if ( m_callback ) {
4387                     m_callback->OnLoadFileError( cs32("Error reading FB3 document") );
4388                 }
4389                 return false;
4390             } else {
4391                 m_container = m_doc->getContainer();
4392                 m_doc_props = m_doc->getProps();
4393                 setRenderProps( 0, 0 );
4394                 REQUEST_RENDER("loadDocument")
4395                 if ( m_callback ) {
4396                     m_callback->OnLoadFileEnd( );
4397                     //m_doc->compact();
4398                     m_doc->dumpStatistics();
4399                 }
4400                 m_arc = m_doc->getContainer();
4401                 return true;
4402             }
4403         }
4404 
4405         if( DetectDocXFormat(m_stream) ) {
4406             CRLog::info("DOCX format detected");
4407             createEmptyDocument();
4408             m_doc->setProps( m_doc_props );
4409             setRenderProps( 0, 0 );
4410             setDocFormat( doc_format_docx );
4411             if ( m_callback )
4412                 m_callback->OnLoadFileFormatDetected(doc_format_docx);
4413             updateDocStyleSheet();
4414             bool res = ImportDocXDocument( m_stream, m_doc, m_callback, this );
4415             if ( !res ) {
4416                 setDocFormat( doc_format_none );
4417                 createDefaultDocument( cs32("ERROR: Error reading DOCX format"), cs32("Cannot open document") );
4418                 if ( m_callback ) {
4419                     m_callback->OnLoadFileError( cs32("Error reading DOCX document") );
4420                 }
4421                 return false;
4422             } else {
4423                 m_container = m_doc->getContainer();
4424                 m_doc_props = m_doc->getProps();
4425                 setRenderProps( 0, 0 );
4426                 REQUEST_RENDER("loadDocument")
4427                 if ( m_callback ) {
4428                     m_callback->OnLoadFileEnd( );
4429                     //m_doc->compact();
4430                     m_doc->dumpStatistics();
4431                 }
4432                 m_arc = m_doc->getContainer();
4433                 return true;
4434             }
4435         }
4436 
4437         if( DetectOpenDocumentFormat(m_stream) ) {
4438             CRLog::info("ODT format detected");
4439             createEmptyDocument();
4440             m_doc->setProps( m_doc_props );
4441             setRenderProps( 0, 0 );
4442             setDocFormat( doc_format_odt );
4443             if ( m_callback )
4444                 m_callback->OnLoadFileFormatDetected(doc_format_odt);
4445             updateDocStyleSheet();
4446             bool res = ImportOpenDocument(m_stream, m_doc, m_callback, this );
4447             if ( !res ) {
4448                 setDocFormat( doc_format_none );
4449                 createDefaultDocument( cs32("ERROR: Error reading DOCX format"), cs32("Cannot open document") );
4450                 if ( m_callback ) {
4451                     m_callback->OnLoadFileError( cs32("Error reading DOCX document") );
4452                 }
4453                 return false;
4454             } else {
4455                 m_container = m_doc->getContainer();
4456                 m_doc_props = m_doc->getProps();
4457                 setRenderProps( 0, 0 );
4458                 REQUEST_RENDER("loadDocument")
4459                 if ( m_callback ) {
4460                     m_callback->OnLoadFileEnd( );
4461                     //m_doc->compact();
4462                     m_doc->dumpStatistics();
4463                 }
4464                 m_arc = m_doc->getContainer();
4465                 return true;
4466             }
4467         }
4468 
4469 #if CHM_SUPPORT_ENABLED==1
4470         if ( DetectCHMFormat( m_stream ) ) {
4471 			// CHM
4472 			CRLog::info("CHM format detected");
4473 			createEmptyDocument();
4474 			m_doc->setProps( m_doc_props );
4475 			setRenderProps( 0, 0 ); // to allow apply styles and rend method while loading
4476 			setDocFormat( doc_format_chm );
4477 			if ( m_callback )
4478 			m_callback->OnLoadFileFormatDetected(doc_format_chm);
4479             updateDocStyleSheet();
4480             bool res = ImportCHMDocument( m_stream, m_doc, m_callback, this );
4481 			if ( !res ) {
4482 				setDocFormat( doc_format_none );
4483                 createDefaultDocument( cs32("ERROR: Error reading CHM format"), cs32("Cannot open document") );
4484 				if ( m_callback ) {
4485                     m_callback->OnLoadFileError( cs32("Error reading CHM document") );
4486 				}
4487 				return false;
4488 			} else {
4489 				setRenderProps( 0, 0 );
4490 				requestRender();
4491 				if ( m_callback ) {
4492 					m_callback->OnLoadFileEnd( );
4493 					//m_doc->compact();
4494 					m_doc->dumpStatistics();
4495 				}
4496 				m_arc = m_doc->getContainer();
4497 				return true;
4498 			}
4499 		}
4500 #endif
4501 
4502 #if ENABLE_ANTIWORD==1
4503         if ( DetectWordFormat( m_stream ) ) {
4504             // DOC
4505             CRLog::info("Word format detected");
4506             createEmptyDocument();
4507             m_doc->setProps( m_doc_props );
4508             setRenderProps( 0, 0 ); // to allow apply styles and rend method while loading
4509             setDocFormat( doc_format_doc );
4510             if ( m_callback )
4511                 m_callback->OnLoadFileFormatDetected(doc_format_doc);
4512             updateDocStyleSheet();
4513             bool res = ImportWordDocument( m_stream, m_doc, m_callback, this );
4514             if ( !res ) {
4515                 setDocFormat( doc_format_none );
4516                 createDefaultDocument( cs32("ERROR: Error reading DOC format"), cs32("Cannot open document") );
4517                 if ( m_callback ) {
4518                     m_callback->OnLoadFileError( cs32("Error reading DOC document") );
4519                 }
4520                 return false;
4521             } else {
4522                 setRenderProps( 0, 0 );
4523                 REQUEST_RENDER("loadDocument")
4524                 if ( m_callback ) {
4525                     m_callback->OnLoadFileEnd( );
4526                     //m_doc->compact();
4527                     m_doc->dumpStatistics();
4528                 }
4529                 m_arc = m_doc->getContainer();
4530                 return true;
4531             }
4532         }
4533 #endif
4534 
4535         m_arc = LVOpenArchieve( m_stream );
4536 		if (!m_arc.isNull())
4537 		{
4538 			m_container = m_arc;
4539 			// archieve
4540 			FileToArcProps( m_doc_props );
4541 			m_container = m_arc;
4542 			m_doc_props->setInt( DOC_PROP_ARC_FILE_COUNT, m_arc->GetObjectCount() );
4543 			bool found = false;
4544 			int htmCount = 0;
4545 			int fb2Count = 0;
4546 			int rtfCount = 0;
4547 			int txtCount = 0;
4548 			int fbdCount = 0;
4549             int pmlCount = 0;
4550 			lString32 defHtml;
4551 			lString32 firstGood;
4552 			for (int i=0; i<m_arc->GetObjectCount(); i++)
4553 			{
4554 				const LVContainerItemInfo * item = m_arc->GetObjectInfo(i);
4555 				if (item)
4556 				{
4557 					if ( !item->IsContainer() )
4558 					{
4559 						lString32 name( item->GetName() );
4560 						CRLog::debug("arc item[%d] : %s", i, LCSTR(name) );
4561 						lString32 s = name;
4562 						s.lowercase();
4563 						bool nameIsOk = true;
4564                         if ( s.endsWith(".htm") || s.endsWith(".html") ) {
4565 							lString32 nm = LVExtractFilenameWithoutExtension( s );
4566                             if ( nm == "index" || nm == "default" )
4567 							defHtml = name;
4568 							htmCount++;
4569                         } else if ( s.endsWith(".fb2") ) {
4570 							fb2Count++;
4571                         } else if ( s.endsWith(".rtf") ) {
4572 							rtfCount++;
4573                         } else if ( s.endsWith(".txt") ) {
4574 							txtCount++;
4575                         } else if ( s.endsWith(".pml") ) {
4576                             pmlCount++;
4577                         } else if ( s.endsWith(".fbd") ) {
4578 							fbdCount++;
4579 						} else {
4580 							nameIsOk = false;
4581 						}
4582 						if ( nameIsOk ) {
4583 							if ( firstGood.empty() )
4584                                 firstGood = name;
4585 						}
4586 						if ( name.length() >= 5 )
4587 						{
4588 							name.lowercase();
4589 							const lChar32 * pext = name.c_str() + name.length() - 4;
4590                             if (!lStr_cmp(pext, ".fb2"))
4591                                 nameIsOk = true;
4592                             else if (!lStr_cmp(pext, ".txt"))
4593                                 nameIsOk = true;
4594                             else if (!lStr_cmp(pext, ".rtf"))
4595                                 nameIsOk = true;
4596 						}
4597 						if ( !nameIsOk )
4598 						continue;
4599 					}
4600 				}
4601 			}
4602 			lString32 fn = !defHtml.empty() ? defHtml : firstGood;
4603 			if ( !fn.empty() ) {
4604 				m_stream = m_arc->OpenStream( fn.c_str(), LVOM_READ );
4605 				if ( !m_stream.isNull() ) {
4606 					CRLog::debug("Opened archive stream %s", LCSTR(fn) );
4607 					m_doc_props->setString(DOC_PROP_FILE_NAME, fn);
4608 					m_doc_props->setString(DOC_PROP_CODE_BASE, LVExtractPath(fn) );
4609 					m_doc_props->setString(DOC_PROP_FILE_SIZE, lString32::itoa((int)m_stream->GetSize()));
4610                     m_doc_props->setHex(DOC_PROP_FILE_CRC32, m_stream->getcrc32());
4611 					found = true;
4612 				}
4613 			}
4614 			// opened archieve stream
4615 			if ( !found )
4616 			{
4617 				Clear();
4618 				if ( m_callback ) {
4619                     m_callback->OnLoadFileError( cs32("File with supported extension not found in archive.") );
4620 				}
4621 				return false;
4622 			}
4623 
4624 		}
4625 		else
4626 
4627 #endif //USE_ZLIB
4628 		{
4629 #if 1
4630 			//m_stream = LVCreateBufferedStream( m_stream, FILE_STREAM_BUFFER_SIZE );
4631 #else
4632 			LVStreamRef stream = LVCreateBufferedStream( m_stream, FILE_STREAM_BUFFER_SIZE );
4633 			lvsize_t sz = stream->GetSize();
4634 			const lvsize_t bufsz = 0x1000;
4635 			lUInt8 buf[bufsz];
4636 			lUInt8 * fullbuf = new lUInt8 [sz];
4637 			stream->SetPos(0);
4638 			stream->Read(fullbuf, sz, NULL);
4639 			lvpos_t maxpos = sz - bufsz;
4640 			for (int i=0; i<1000; i++)
4641 			{
4642 				lvpos_t pos = (lvpos_t)((((lUInt64)i) * 1873456178) % maxpos);
4643 				stream->SetPos( pos );
4644 				lvsize_t readsz = 0;
4645 				stream->Read( buf, bufsz, &readsz );
4646 				if (readsz != bufsz)
4647 				{
4648 					//
4649 					fprintf(stderr, "data read error!\n");
4650 				}
4651 				for (int j=0; j<bufsz; j++)
4652 				{
4653 					if (fullbuf[pos+j] != buf[j])
4654 					{
4655 						fprintf(stderr, "invalid data!\n");
4656 					}
4657 				}
4658 			}
4659 			delete[] fullbuf;
4660 #endif
4661 			LVStreamRef tcrDecoder = LVCreateTCRDecoderStream(m_stream);
4662 			if (!tcrDecoder.isNull())
4663 				m_stream = tcrDecoder;
4664 		}
4665 
4666         // TEST FB2 Coverpage parser
4667     #if 0
4668         LVStreamRef cover = GetFB2Coverpage(m_stream);
4669         if (!cover.isNull()) {
4670             CRLog::info("cover page found: %d bytes", (int)cover->GetSize());
4671             LVImageSourceRef img = LVCreateStreamImageSource(cover);
4672             if (!img.isNull()) {
4673                 CRLog::info("image size %d x %d", img->GetWidth(), img->GetHeight());
4674                 LVColorDrawBuf buf(200, 200);
4675                 CRLog::info("trying to draw");
4676                 buf.Draw(img, 0, 0, 200, 200, true);
4677             }
4678         }
4679     #endif
4680 
4681 		return ParseDocument();
4682 
4683 	}
4684 }
4685 
getDocFormatName(doc_format_t fmt)4686 const lChar32 * getDocFormatName(doc_format_t fmt) {
4687 	switch (fmt) {
4688 	case doc_format_fb2:
4689 		return U"FictionBook (FB2)";
4690 	case doc_format_fb3:
4691 		return U"FictionBook (FB3)";
4692 	case doc_format_txt:
4693 		return U"Plain text (TXT)";
4694 	case doc_format_rtf:
4695 		return U"Rich text (RTF)";
4696 	case doc_format_epub:
4697 		return U"EPUB";
4698 	case doc_format_chm:
4699 		return U"CHM";
4700 	case doc_format_html:
4701 		return U"HTML";
4702 	case doc_format_txt_bookmark:
4703 		return U"CR3 TXT Bookmark";
4704 	case doc_format_doc:
4705 		return U"DOC";
4706 	case doc_format_docx:
4707 		return U"DOCX";
4708     case doc_format_odt:
4709         return U"OpenDocument (ODT)";
4710 	default:
4711 		return U"Unknown format";
4712 	}
4713 }
4714 
4715 /// sets current document format
setDocFormat(doc_format_t fmt)4716 void LVDocView::setDocFormat(doc_format_t fmt) {
4717 	m_doc_format = fmt;
4718 	lString32 desc(getDocFormatName(fmt));
4719 	m_doc_props->setString(DOC_PROP_FILE_FORMAT, desc);
4720 	m_doc_props->setInt(DOC_PROP_FILE_FORMAT_ID, (int) fmt);
4721 }
4722 
4723 /// create document and set flags
createEmptyDocument()4724 void LVDocView::createEmptyDocument() {
4725 	_posIsSet = false;
4726 	m_swapDone = false;
4727 	_posBookmark = ldomXPointer();
4728 	//lUInt32 saveFlags = 0;
4729 
4730 	//m_doc ? m_doc->getDocFlags() : DOC_FLAG_DEFAULTS;
4731 	m_is_rendered = false;
4732 	if (m_doc)
4733 		delete m_doc;
4734 	m_doc = new ldomDocument();
4735 	m_cursorPos.clear();
4736 	m_markRanges.clear();
4737         m_bmkRanges.clear();
4738 	_posBookmark.clear();
4739 	m_section_bounds.clear();
4740 	m_section_bounds_valid = false;
4741 	_posIsSet = false;
4742 	m_swapDone = false;
4743 
4744 	m_doc->setProps(m_doc_props);
4745 	m_doc->setDocFlags(0);
4746 	m_doc->setDocFlag(DOC_FLAG_PREFORMATTED_TEXT, m_props->getBoolDef(
4747 			PROP_TXT_OPTION_PREFORMATTED, false));
4748 	m_doc->setDocFlag(DOC_FLAG_ENABLE_FOOTNOTES, m_props->getBoolDef(
4749 			PROP_FOOTNOTES, true));
4750 	m_doc->setDocFlag(DOC_FLAG_ENABLE_INTERNAL_STYLES, m_props->getBoolDef(
4751 			PROP_EMBEDDED_STYLES, true));
4752     m_doc->setDocFlag(DOC_FLAG_ENABLE_DOC_FONTS, m_props->getBoolDef(
4753             PROP_EMBEDDED_FONTS, true));
4754     m_doc->setDocFlag(DOC_FLAG_NONLINEAR_PAGEBREAK, m_props->getBoolDef(
4755             PROP_NONLINEAR_PAGEBREAK, false));
4756     m_doc->setSpaceWidthScalePercent(m_props->getIntDef(PROP_FORMAT_SPACE_WIDTH_SCALE_PERCENT, DEF_SPACE_WIDTH_SCALE_PERCENT));
4757     m_doc->setMinSpaceCondensingPercent(m_props->getIntDef(PROP_FORMAT_MIN_SPACE_CONDENSING_PERCENT, DEF_MIN_SPACE_CONDENSING_PERCENT));
4758     m_doc->setUnusedSpaceThresholdPercent(m_props->getIntDef(PROP_FORMAT_UNUSED_SPACE_THRESHOLD_PERCENT, DEF_UNUSED_SPACE_THRESHOLD_PERCENT));
4759     m_doc->setMaxAddedLetterSpacingPercent(m_props->getIntDef(PROP_FORMAT_MAX_ADDED_LETTER_SPACING_PERCENT, DEF_MAX_ADDED_LETTER_SPACING_PERCENT));
4760     m_doc->setHangingPunctiationEnabled(m_props->getBoolDef(PROP_FLOATING_PUNCTUATION, false));
4761     m_doc->setRenderBlockRenderingFlags(m_props->getIntDef(PROP_RENDER_BLOCK_RENDERING_FLAGS, BLOCK_RENDERING_FLAGS_DEFAULT));
4762     m_doc->setDOMVersionRequested(m_props->getIntDef(PROP_REQUESTED_DOM_VERSION, gDOMVersionCurrent));
4763     if (m_def_interline_space == 100) // (avoid any rounding issue)
4764         m_doc->setInterlineScaleFactor(INTERLINE_SCALE_FACTOR_NO_SCALE);
4765     else
4766         m_doc->setInterlineScaleFactor(INTERLINE_SCALE_FACTOR_NO_SCALE * m_def_interline_space / 100);
4767 
4768     m_doc->setContainer(m_container);
4769     // This sets the element names default style (display, whitespace)
4770     // as defined in fb2def.h (createEmptyDocument() is called for all
4771     // document formats, so FB2 and HTML starts with the fb2def.h styles.
4772     // They are then updated with the <format>.css files, but they get back
4773     // these original styles when one selects "Clear all external styles".
4774     m_doc->setNodeTypes(fb2_elem_table);
4775     m_doc->setAttributeTypes(fb2_attr_table);
4776     m_doc->setNameSpaceTypes(fb2_ns_table);
4777 }
4778 
4779 /// format of document from cache is known
OnCacheFileFormatDetected(doc_format_t fmt)4780 void LVDocView::OnCacheFileFormatDetected( doc_format_t fmt )
4781 {
4782     // update document format id
4783     m_doc_format = fmt;
4784     // notify about format detection, to allow setting format-specific CSS
4785     if (m_callback) {
4786         m_callback->OnLoadFileFormatDetected(getDocFormat());
4787     }
4788     // set stylesheet
4789     updateDocStyleSheet();
4790 }
4791 
insertBookmarkPercentInfo(int start_page,int end_y,int percent)4792 void LVDocView::insertBookmarkPercentInfo(int start_page, int end_y, int percent)
4793 {
4794     CR_UNUSED3(start_page, end_y, percent);
4795 #if 0
4796     for (int j = start_page; j < m_pages.length(); j++) {
4797         if (m_pages[j]->start > end_y)
4798             break;
4799         LVBookMarkPercentInfo *bmi = m_bookmarksPercents[j];
4800         if (bmi == NULL) {
4801             bmi = new LVBookMarkPercentInfo(1, percent);
4802             m_bookmarksPercents.set(j, bmi);
4803         } else
4804             bmi->add(percent);
4805     }
4806 #endif
4807 }
4808 
ParseDocument()4809 bool LVDocView::ParseDocument() {
4810 
4811 	createEmptyDocument();
4812 
4813 	if (m_stream->GetSize() > DOCUMENT_CACHING_MIN_SIZE) {
4814 		// try loading from cache
4815 
4816 		//lString32 fn( m_stream->GetName() );
4817 		lString32 fn =
4818 				m_doc_props->getStringDef(DOC_PROP_FILE_NAME, "untitled");
4819 		fn = LVExtractFilename(fn);
4820 		lUInt32 crc = 0;
4821         m_stream->getcrc32(crc);
4822 		CRLog::debug("Check whether document %s crc %08x exists in cache",
4823 				UnicodeToUtf8(fn).c_str(), crc);
4824 
4825 		// set stylesheet
4826         updateDocStyleSheet();
4827 		//m_doc->getStyleSheet()->clear();
4828 		//m_doc->getStyleSheet()->parse(m_stylesheet.c_str());
4829 
4830 		setRenderProps(0, 0); // to allow apply styles and rend method while loading
4831         if (m_doc->openFromCache(this, m_callback)) {
4832 			CRLog::info("Document is found in cache, will reuse");
4833 
4834 
4835 			//            lString32 docstyle = m_doc->createXPointer(U"/FictionBook/stylesheet").getText();
4836 			//            if ( !docstyle.empty() && m_doc->getDocFlag(DOC_FLAG_ENABLE_INTERNAL_STYLES) ) {
4837 			//                //m_doc->getStyleSheet()->parse(UnicodeToUtf8(docstyle).c_str());
4838 			//                m_doc->setStyleSheet( UnicodeToUtf8(docstyle).c_str(), false );
4839 			//            }
4840 
4841 			m_showCover = !getCoverPageImage().isNull();
4842 
4843             if ( m_callback )
4844                 m_callback->OnLoadFileEnd( );
4845 
4846 			return true;
4847 		}
4848 		CRLog::info("Cannot get document from cache, parsing...");
4849 	}
4850 
4851 	{
4852         ldomDocumentWriter writer(m_doc);
4853         ldomDocumentWriterFilter writerFilter(m_doc, false, HTML_AUTOCLOSE_TABLE);
4854         lString32 txt_autodet_lang;
4855         // Note: creating these 2 writers here, and using only one,
4856         // will still have both their destructors called when
4857         // leaving this scope. Each destructor call will have
4858         // ldomDocumentWriter::~ldomDocumentWriter() called, and
4859         // both will do the same work on m_doc. So, beware there
4860         // that this causes no issue.
4861         // We might want to refactor this section to avoid any issue.
4862 
4863         LVFileFormatParser * parser = NULL;
4864         if (m_stream->GetSize() >= 5) {
4865             // Only check the following formats when the document is
4866             // at least 5 bytes: if not, go directly with plain text.
4867 
4868             /// FB2 format
4869             setDocFormat( doc_format_fb2);
4870             parser = new LVXMLParser(m_stream, &writer, false, true);
4871             if (!parser->CheckFormat()) {
4872                 delete parser;
4873                 parser = NULL;
4874             }
4875 
4876             /// RTF format
4877             if (parser == NULL) {
4878                 setDocFormat( doc_format_rtf);
4879                 parser = new LVRtfParser(m_stream, &writer);
4880                 if (!parser->CheckFormat()) {
4881                     delete parser;
4882                     parser = NULL;
4883                 }
4884             }
4885 
4886             /// HTML format
4887             if (parser == NULL) {
4888                 setDocFormat( doc_format_html);
4889                 parser = new LVHTMLParser(m_stream, &writerFilter);
4890                 if (!parser->CheckFormat()) {
4891                     delete parser;
4892                     parser = NULL;
4893                 }
4894             }
4895 
4896             ///cool reader bookmark in txt format
4897             if (parser == NULL) {
4898                 //m_text_format = txt_format_pre; // DEBUG!!!
4899                 setDocFormat( doc_format_txt_bookmark);
4900                 parser = new LVTextBookmarkParser(m_stream, &writer);
4901                 if (!parser->CheckFormat()) {
4902                     delete parser;
4903                     parser = NULL;
4904                 }
4905             }
4906         }
4907 
4908         /// plain text format
4909         if (parser == NULL) {
4910             //m_text_format = txt_format_pre; // DEBUG!!!
4911             setDocFormat( doc_format_txt);
4912             parser = new LVTextParser(m_stream, &writer,
4913                             getTextFormatOptions() == txt_format_pre);
4914             if (!parser->CheckFormat()) {
4915                 delete parser;
4916                 parser = NULL;
4917             } else {
4918                 txt_autodet_lang = ((LVTextParser*)parser)->GetLangName();
4919             }
4920 
4921         }
4922 
4923         /// plain text format (robust, never fail)
4924         if (parser == NULL) {
4925             setDocFormat( doc_format_txt);
4926             parser = new LVTextRobustParser(m_stream, &writer,
4927                              getTextFormatOptions() == txt_format_pre);
4928             if (!parser->CheckFormat()) {
4929                 // Never reached
4930                 delete parser;
4931                 parser = NULL;
4932             }
4933         }
4934 
4935         // unknown format (never reached)
4936         if (!parser) {
4937             setDocFormat( doc_format_none);
4938             createDefaultDocument(cs32("ERROR: Unknown document format"),
4939                     cs32("Cannot open document"));
4940             if (m_callback) {
4941                 m_callback->OnLoadFileError(
4942                         cs32("Unknown document format"));
4943             }
4944             return false;
4945         }
4946 
4947 		if (m_callback) {
4948 			m_callback->OnLoadFileFormatDetected(getDocFormat());
4949 		}
4950         updateDocStyleSheet();
4951 		setRenderProps(0, 0);
4952 
4953 		// set stylesheet
4954 		//m_doc->getStyleSheet()->clear();
4955 		//m_doc->getStyleSheet()->parse(m_stylesheet.c_str());
4956 		//m_doc->setStyleSheet( m_stylesheet.c_str(), true );
4957 
4958 		// parse
4959 		parser->setProgressCallback(m_callback);
4960 		if (!parser->Parse()) {
4961 			delete parser;
4962 			if (m_callback) {
4963                 m_callback->OnLoadFileError(cs32("Bad document format"));
4964 			}
4965             createDefaultDocument(cs32("ERROR: Bad document format"),
4966                     cs32("Cannot open document"));
4967 			return false;
4968 		}
4969 		delete parser;
4970 		_pos = 0;
4971 		_page = 0;
4972 
4973 		//m_doc->compact();
4974 		m_doc->dumpStatistics();
4975 
4976 		if (m_doc_format == doc_format_html) {
4977 			static lUInt16 path[] = { el_html, el_head, el_title, 0 };
4978 			ldomNode * el = NULL;
4979 			ldomNode * rootNode = m_doc->getRootNode();
4980 			if (rootNode)
4981 				el = rootNode->findChildElement(path);
4982 			if (el != NULL) {
4983                 lString32 s = el->getText(U' ', 1024);
4984 				if (!s.empty()) {
4985 					m_doc_props->setString(DOC_PROP_TITLE, s);
4986 				}
4987 			}
4988 		}
4989 
4990 		//        lString32 docstyle = m_doc->createXPointer(U"/FictionBook/stylesheet").getText();
4991 		//        if ( !docstyle.empty() && m_doc->getDocFlag(DOC_FLAG_ENABLE_INTERNAL_STYLES) ) {
4992 		//            //m_doc->getStyleSheet()->parse(UnicodeToUtf8(docstyle).c_str());
4993 		//            m_doc->setStyleSheet( UnicodeToUtf8(docstyle).c_str(), false );
4994 		//        }
4995 
4996 #ifdef SAVE_COPY_OF_LOADED_DOCUMENT //def _DEBUG
4997 		LVStreamRef ostream = LVOpenFileStream( "test_save_source.xml", LVOM_WRITE );
4998 		m_doc->saveToStream( ostream, "utf-16" );
4999 #endif
5000 #if 0
5001 		{
5002 			LVStreamRef ostream = LVOpenFileStream( "test_save.fb2", LVOM_WRITE );
5003 			m_doc->saveToStream( ostream, "utf-16" );
5004 			m_doc->getRootNode()->recurseElements( SaveBase64Objects );
5005 		}
5006 #endif
5007 
5008 		//m_doc->getProps()->clear();
5009 		if (m_doc_props->getStringDef(DOC_PROP_TITLE, "").empty()) {
5010 			m_doc_props->setString(DOC_PROP_AUTHORS, extractDocAuthors(m_doc));
5011 			m_doc_props->setString(DOC_PROP_TITLE, extractDocTitle(m_doc));
5012 			if (txt_autodet_lang.length() > 0)      // true only for doc_format_txt
5013 				m_doc_props->setString(DOC_PROP_LANGUAGE, txt_autodet_lang);
5014 			else
5015 				m_doc_props->setString(DOC_PROP_LANGUAGE, extractDocLanguage(m_doc));
5016 			m_doc_props->setString(DOC_PROP_KEYWORDS, extractDocKeywords(m_doc));
5017 			m_doc_props->setString(DOC_PROP_DESCRIPTION, extractDocDescription(m_doc));
5018             int seriesNumber = -1;
5019             lString32 seriesName = extractDocSeries(m_doc, &seriesNumber);
5020             m_doc_props->setString(DOC_PROP_SERIES_NAME, seriesName);
5021             m_doc_props->setString(DOC_PROP_SERIES_NUMBER, seriesNumber>0 ? lString32::itoa(seriesNumber) :lString32::empty_str);
5022         }
5023 	}
5024 	//m_doc->persist();
5025 
5026 	m_showCover = !getCoverPageImage().isNull();
5027 
5028     REQUEST_RENDER("loadDocument")
5029 	if (m_callback) {
5030 		m_callback->OnLoadFileEnd();
5031 	}
5032 
5033 #if 0 // test serialization
5034 	SerialBuf buf( 1024 );
5035 	m_doc->serializeMaps(buf);
5036 	if ( !buf.error() ) {
5037 		int sz = buf.pos();
5038 		SerialBuf buf2( buf.buf(), buf.pos() );
5039 		ldomDocument * newdoc = new ldomDocument();
5040 		if ( newdoc->deserializeMaps( buf2 ) ) {
5041 			delete newdoc;
5042 		}
5043 	}
5044 #endif
5045 #if 0// test swap to disk
5046     lString32 cacheFile = cs32("/tmp/cr3swap.bin");
5047 	bool res = m_doc->swapToCacheFile( cacheFile );
5048 	if ( !res ) {
5049 		CRLog::error( "Failed to swap to disk" );
5050 		return false;
5051 	}
5052 #endif
5053 #if 0 // test restore from swap
5054 	delete m_doc;
5055 	m_doc = new ldomDocument();
5056 	res = m_doc->openFromCacheFile( cacheFile );
5057 	m_doc->setDocFlags( saveFlags );
5058 	m_doc->setContainer( m_container );
5059 	if ( !res ) {
5060 		CRLog::error( "Failed loading of swap from disk" );
5061 		return false;
5062 	}
5063 	m_doc->getStyleSheet()->clear();
5064 	m_doc->getStyleSheet()->parse(m_stylesheet.c_str());
5065 #endif
5066 #if 0
5067 	{
5068 		LVStreamRef ostream = LVOpenFileStream(U"out.xml", LVOM_WRITE );
5069 		if ( !ostream.isNull() )
5070 		m_doc->saveToStream( ostream, "utf-8" );
5071 	}
5072 #endif
5073 
5074 	return true;
5075 }
5076 
5077 /// save unsaved data to cache file (if one is created), with timeout option
updateCache(CRTimerUtil & maxTime)5078 ContinuousOperationResult LVDocView::updateCache(CRTimerUtil & maxTime)
5079 {
5080     return m_doc->updateMap(maxTime);
5081 }
5082 
5083 /// save unsaved data to cache file (if one is created), w/o timeout
updateCache()5084 ContinuousOperationResult LVDocView::updateCache()
5085 {
5086     CRTimerUtil infinite;
5087     return swapToCache(infinite);
5088 }
5089 
5090 /// save document to cache file, with timeout option
swapToCache(CRTimerUtil & maxTime)5091 ContinuousOperationResult LVDocView::swapToCache(CRTimerUtil & maxTime)
5092 {
5093     int fs = m_doc_props->getIntDef(DOC_PROP_FILE_SIZE, 0);
5094     CRLog::trace("LVDocView::swapToCache(fs = %d)", fs);
5095     // minimum file size to swap, even if forced
5096     // TODO
5097     int mfs = 30000; //m_props->getIntDef(PROP_FORCED_MIN_FILE_SIZE_TO_CACHE, 30000); // 30K
5098     if (fs < mfs) {
5099         //CRLog::trace("LVDocView::swapToCache : file is too small for caching");
5100         return CR_DONE;
5101     }
5102     return m_doc->swapToCache( maxTime );
5103 }
5104 
swapToCache()5105 void LVDocView::swapToCache() {
5106     CRTimerUtil infinite;
5107     swapToCache(infinite);
5108     m_swapDone = true;
5109 }
5110 
LoadDocument(const char * fname,bool metadataOnly)5111 bool LVDocView::LoadDocument(const char * fname, bool metadataOnly) {
5112 	if (!fname || !fname[0])
5113 		return false;
5114 	return LoadDocument(LocalToUnicode(lString8(fname)).c_str(), metadataOnly);
5115 }
5116 
5117 /// returns XPointer to middle paragraph of current page
getCurrentPageMiddleParagraph()5118 ldomXPointer LVDocView::getCurrentPageMiddleParagraph() {
5119 	LVLock lock(getMutex());
5120 	checkPos();
5121 	ldomXPointer ptr;
5122 	if (!m_doc)
5123 		return ptr;
5124 
5125 	if (getViewMode() == DVM_SCROLL) {
5126 		// SCROLL mode
5127 		int starty = _pos;
5128 		int endy = _pos + m_dy;
5129 		int fh = GetFullHeight();
5130 		if (endy >= fh)
5131 			endy = fh - 1;
5132 		ptr = m_doc->createXPointer(lvPoint(0, (starty + endy) / 2));
5133 	} else {
5134 		// PAGES mode
5135 		int pageIndex = getCurPage();
5136 		if (pageIndex < 0 || pageIndex >= m_pages.length())
5137 			pageIndex = getCurPage();
5138 		if (pageIndex >= 0 && pageIndex < m_pages.length()) {
5139 			LVRendPageInfo *page = m_pages[pageIndex];
5140 			if (page->flags & RN_PAGE_TYPE_NORMAL)
5141 				ptr = m_doc->createXPointer(lvPoint(0, page->start + page->height / 2));
5142 		}
5143 	}
5144 	if (ptr.isNull())
5145 		return ptr;
5146 	ldomXPointerEx p(ptr);
5147 	if (!p.isVisibleFinal())
5148 		if (!p.ensureFinal())
5149 			if (!p.prevVisibleFinal())
5150 				if (!p.nextVisibleFinal())
5151 					return ptr;
5152 	return ldomXPointer(p);
5153 }
5154 
5155 /// returns bookmark
getBookmark(bool precise)5156 ldomXPointer LVDocView::getBookmark( bool precise ) {
5157 	LVLock lock(getMutex());
5158 	checkPos();
5159 	ldomXPointer ptr;
5160 	if (m_doc) {
5161 		if (isPageMode()) {
5162 			if (_page >= 0 && _page < m_pages.length()) {
5163 				// In some edge cases, getting the xpointer for y=m_pages[_page]->start
5164 				// could resolve back to the previous page. We need to check for that
5165 				// and increase y until we find a coherent one.
5166 				// (In the following, we always want to find the first logical word/char.)
5167 				LVRendPageInfo * page = m_pages[_page];
5168 				bool found = false;
5169 				ldomXPointer fallback_ptr;
5170 				if (precise) {
5171 					for (int y = page->start; y < page->start + page->height; y++) {
5172 						ptr = m_doc->createXPointer(lvPoint(0, y),
5173 													PT_DIR_SCAN_FORWARD_LOGICAL_FIRST);
5174 						lvPoint pt = ptr.toPoint();
5175 						if (pt.y >= page->start) {
5176 							if (!fallback_ptr)
5177 								fallback_ptr = ptr;
5178 							if (pt.y < page->start + page->height) {
5179 								// valid, resolves back to same page
5180 								found = true;
5181 								break;
5182 							}
5183 						}
5184 					}
5185 				} else {
5186 					ptr = m_doc->createXPointer(lvPoint(0, page->start));
5187 					found = true;
5188 				}
5189 				if (!found) {
5190 					// None looking forward resolved to that same page, we
5191 					// might find a better one looking backward (eg: when an
5192 					// element contains some floats that overflows its height).
5193 					ptr = m_doc->createXPointer(lvPoint(0, page->start), PT_DIR_SCAN_BACKWARD_LOGICAL_FIRST);
5194 					lvPoint pt = ptr.toPoint();
5195 					if (pt.y >= page->start && pt.y < page->start + page->height ) {
5196 						found = true;
5197 					}
5198 				}
5199 				if (!found) {
5200 					if (!fallback_ptr.isNull()) {
5201 						ptr = fallback_ptr;
5202 					}
5203 					else {
5204 						// fallback to the one for page->start, even if not good
5205 						ptr = m_doc->createXPointer(lvPoint(0, page->start), PT_DIR_SCAN_BACKWARD_LOGICAL_FIRST);
5206 					}
5207 				}
5208 			}
5209 		} else {
5210 			// In scroll mode, the y position may not resolve to any xpointer
5211 			// (because of margins, empty elements...)
5212 			// When inside an image (top of page being the middle of an image),
5213 			// we get the top of the image, and when restoring this position,
5214 			// we'll have the top of the image at the top of the page, so
5215 			// scrolling a bit up.
5216 			// Let's do the same in that case: get the previous text node
5217 			// position
5218 			if (precise) {
5219 				for (int y = _pos; y >= 0; y--) {
5220 					ptr = m_doc->createXPointer(lvPoint(0, y), PT_DIR_SCAN_BACKWARD_LOGICAL_FIRST);
5221 					if (!ptr.isNull())
5222 						break;
5223 				}
5224 			} else {
5225 				ptr = m_doc->createXPointer(lvPoint(0, _pos));
5226 			}
5227 		}
5228 	}
5229 	return ptr;
5230 	/*
5231 	 lUInt64 pos = m_pos;
5232 	 if (m_view_mode==DVM_PAGES)
5233 	 m_pos += m_dy/3;
5234 	 lUInt64 h = GetFullHeight();
5235 	 if (h<1)
5236 	 h = 1;
5237 	 return pos*1000000/h;
5238 	 */
5239 }
5240 
5241 /// returns bookmark for specified page
getPageBookmark(int page)5242 ldomXPointer LVDocView::getPageBookmark(int page) {
5243 	LVLock lock(getMutex());
5244     CHECK_RENDER("getPageBookmark()")
5245 	if (page < 0 || page >= m_pages.length())
5246 		return ldomXPointer();
5247 	ldomXPointer ptr = m_doc->createXPointer(lvPoint(0, m_pages[page]->start));
5248 	return ptr;
5249 }
5250 
5251 /// get bookmark position text
getBookmarkPosText(ldomXPointer bm,lString32 & titleText,lString32 & posText)5252 bool LVDocView::getBookmarkPosText(ldomXPointer bm, lString32 & titleText,
5253 		lString32 & posText) {
5254 	LVLock lock(getMutex());
5255 	checkRender();
5256     titleText = posText = lString32::empty_str;
5257 	if (bm.isNull())
5258 		return false;
5259 	ldomNode * el = bm.getNode();
5260 	CRLog::trace("getBookmarkPosText() : getting position text");
5261 	if (el->isText()) {
5262         lString32 txt = bm.getNode()->getText();
5263 		int startPos = bm.getOffset();
5264 		int len = txt.length() - startPos;
5265 		if (len > 0)
5266 			txt = txt.substr(startPos, len);
5267 		if (startPos > 0)
5268             posText = "...";
5269         posText += txt;
5270 		el = el->getParentNode();
5271 	} else {
5272         posText = el->getText(U' ', 1024);
5273 	}
5274     bool inTitle = false;
5275 	do {
5276 		while (el && el->getNodeId() != el_section && el->getNodeId()
5277 				!= el_body) {
5278 			if (el->getNodeId() == el_title || el->getNodeId() == el_subtitle)
5279 				inTitle = true;
5280 			el = el->getParentNode();
5281 		}
5282 		if (el) {
5283 			if (inTitle) {
5284 				posText.clear();
5285 				if (el->getChildCount() > 1) {
5286 					ldomNode * node = el->getChildNode(1);
5287                     posText = node->getText(' ', 8192);
5288 				}
5289 				inTitle = false;
5290 			}
5291 			if (el->getNodeId() == el_body && !titleText.empty())
5292 				break;
5293 			lString32 txt = getSectionHeader(el);
5294 			lChar32 lastch = !txt.empty() ? txt[txt.length() - 1] : 0;
5295 			if (!titleText.empty()) {
5296 				if (lastch != '.' && lastch != '?' && lastch != '!')
5297                     txt += ".";
5298                 txt += " ";
5299 			}
5300 			titleText = txt + titleText;
5301 			el = el->getParentNode();
5302 		}
5303 		if (titleText.length() > 50)
5304 			break;
5305 	} while (el);
5306     limitStringSize(titleText, 70);
5307 	limitStringSize(posText, 120);
5308 	return true;
5309 }
5310 
5311 /// moves position to bookmark
goToBookmark(ldomXPointer bm)5312 void LVDocView::goToBookmark(ldomXPointer bm) {
5313 	LVLock lock(getMutex());
5314     CHECK_RENDER("goToBookmark()")
5315 	_posIsSet = false;
5316 	_posBookmark = bm;
5317 }
5318 
5319 /// get page number by bookmark
getBookmarkPage(ldomXPointer bm)5320 int LVDocView::getBookmarkPage(ldomXPointer bm) {
5321 	LVLock lock(getMutex());
5322     CHECK_RENDER("getBookmarkPage()")
5323 	if (bm.isNull()) {
5324 		return 0;
5325 	} else {
5326 		lvPoint pt = bm.toPoint();
5327 		if (pt.y < 0)
5328 			return 0;
5329 		return m_pages.FindNearestPage(pt.y, 0);
5330 	}
5331 }
5332 
updateScroll()5333 void LVDocView::updateScroll() {
5334 	checkPos();
5335 	if (m_view_mode == DVM_SCROLL) {
5336 		int npos = _pos;
5337 		int fh = GetFullHeight();
5338 		int shift = 0;
5339 		int npage = m_dy;
5340 		while (fh > 16384) {
5341 			fh >>= 1;
5342 			npos >>= 1;
5343 			npage >>= 1;
5344 			shift++;
5345 		}
5346 		if (npage < 1)
5347 			npage = 1;
5348 		m_scrollinfo.pos = npos;
5349 		m_scrollinfo.maxpos = fh - npage;
5350 		m_scrollinfo.pagesize = npage;
5351 		m_scrollinfo.scale = shift;
5352 		char str[32];
5353 		sprintf(str, "%d%%", (int) (fh > 0 ? (100 * npos / fh) : 0));
5354 		m_scrollinfo.posText = lString32(str);
5355 	} else {
5356 		int page = getCurPage();
5357 		int vpc = getVisiblePageCount();
5358 		m_scrollinfo.pos = page / vpc;
5359 		m_scrollinfo.maxpos = (m_pages.length() + vpc - 1) / vpc - 1;
5360         m_scrollinfo.pagesize = 1; //getVisiblePageCount();
5361 		m_scrollinfo.scale = 0;
5362 		char str[32] = { 0 };
5363 		if (m_pages.length() > 1) {
5364 			if (page <= 0) {
5365 				sprintf(str, "cover");
5366 			} else
5367 				sprintf(str, "%d / %d", page, m_pages.length() - 1);
5368 		}
5369 		m_scrollinfo.posText = lString32(str);
5370 	}
5371 }
5372 
5373 /// move to position specified by scrollbar
goToScrollPos(int pos)5374 bool LVDocView::goToScrollPos(int pos) {
5375 	if (m_view_mode == DVM_SCROLL) {
5376 		SetPos(scrollPosToDocPos(pos));
5377 		return true;
5378 	} else {
5379 		int vpc = this->getVisiblePageCount();
5380 		int curPage = getCurPage();
5381 		pos = pos * vpc;
5382 		if (pos >= getPageCount())
5383 			pos = getPageCount() - 1;
5384 		if (pos < 0)
5385 			pos = 0;
5386 		if (curPage == pos)
5387 			return false;
5388 		goToPage(pos);
5389 		return true;
5390 	}
5391 }
5392 
5393 /// converts scrollbar pos to doc pos
scrollPosToDocPos(int scrollpos)5394 int LVDocView::scrollPosToDocPos(int scrollpos) {
5395 	if (m_view_mode == DVM_SCROLL) {
5396 		int n = scrollpos << m_scrollinfo.scale;
5397 		if (n < 0)
5398 			n = 0;
5399 		int fh = GetFullHeight();
5400 		if (n > fh)
5401 			n = fh;
5402 		return n;
5403 	} else {
5404 		int vpc = getVisiblePageCount();
5405 		int n = scrollpos * vpc;
5406 		if (!m_pages.length())
5407 			return 0;
5408 		if (n >= m_pages.length())
5409 			n = m_pages.length() - 1;
5410 		if (n < 0)
5411 			n = 0;
5412 		return m_pages[n]->start;
5413 	}
5414 }
5415 
5416 /// get list of links
getCurrentPageLinks(ldomXRangeList & list)5417 void LVDocView::getCurrentPageLinks(ldomXRangeList & list) {
5418 	list.clear();
5419 	/// get page document range, -1 for current page
5420 	LVRef < ldomXRange > page = getPageDocumentRange();
5421 	if (!page.isNull()) {
5422 		// search for links
5423 		class LinkKeeper: public ldomNodeCallback {
5424 			ldomXRangeList &_list;
5425 			bool check_first_text_node_parents_done;
5426 		public:
5427 			LinkKeeper(ldomXRangeList & list) : _list(list) {
5428 				check_first_text_node_parents_done = false;
5429 			}
5430 			/// called for each found text fragment in range
5431 			virtual void onText(ldomXRange * r) {
5432 				if (check_first_text_node_parents_done)
5433 					return;
5434 				// For the first text node, look at its parents
5435 				// as one might be a <A>
5436 				ldomNode * node = r->getStart().getNode();
5437 				while ( node && !node->isElement() )
5438 					node = node->getParentNode();
5439 				while ( node && node->getNodeId() != el_a )
5440 					node = node->getParentNode();
5441 				if ( node ) { // <a> parent of first text node found
5442 					ldomXPointerEx xp = ldomXPointerEx(node, 0);
5443 					onElement( &xp );
5444 				}
5445 				check_first_text_node_parents_done = true;
5446 			}
5447 			/// called for each found node in range
5448 			virtual bool onElement(ldomXPointerEx * ptr) {
5449 				//
5450 				ldomNode * elem = ptr->getNode();
5451 				if (elem->getNodeId() == el_a) {
5452 					for (int i = 0; i < _list.length(); i++) {
5453 						if (_list[i]->getStart().getNode() == elem)
5454 							return true; // don't add, duplicate found!
5455 					}
5456 					// We ignore <a/> and <a></a> with no content, as we
5457 					// can't make a ldomXRange out of them
5458 					// We also want all the <a> content, so we use a ldomXRange()
5459 					// extended to include its deepest text child
5460 					if (elem->hasChildren())
5461 						_list.add(new ldomXRange(elem, true));
5462 				}
5463 				return true;
5464 			}
5465 		};
5466 		LinkKeeper callback(list);
5467 		page->forEach(&callback);
5468 		if (m_view_mode == DVM_PAGES && getVisiblePageCount() > 1) {
5469 			// process second page
5470 			int pageNumber = getCurPage();
5471 			page = getPageDocumentRange(pageNumber + 1);
5472 			if (!page.isNull())
5473 				page->forEach(&callback);
5474 		}
5475 	}
5476 }
5477 
5478 /// returns document offset for next page
getNextPageOffset()5479 int LVDocView::getNextPageOffset() {
5480 	LVLock lock(getMutex());
5481 	checkPos();
5482 	if (isScrollMode()) {
5483 		return GetPos() + m_dy;
5484 	} else {
5485 		int p = getCurPage() + getVisiblePageCount();
5486 		if (p < m_pages.length())
5487 			return m_pages[p]->start;
5488 		if (!p || m_pages.length() == 0)
5489 			return 0;
5490 		return m_pages[m_pages.length() - 1]->start;
5491 	}
5492 }
5493 
5494 /// returns document offset for previous page
getPrevPageOffset()5495 int LVDocView::getPrevPageOffset() {
5496 	LVLock lock(getMutex());
5497 	checkPos();
5498 	if (m_view_mode == DVM_SCROLL) {
5499 		return GetPos() - m_dy;
5500 	} else {
5501 		int p = getCurPage();
5502 		p -= getVisiblePageCount();
5503 		if (p < 0)
5504 			p = 0;
5505 		if (p >= m_pages.length())
5506 			return 0;
5507 		return m_pages[p]->start;
5508 	}
5509 }
5510 
addItem(LVPtrVector<LVTocItem,false> & items,LVTocItem * item)5511 static void addItem(LVPtrVector<LVTocItem, false> & items, LVTocItem * item) {
5512 	if (item->getLevel() > 0)
5513 		items.add(item);
5514 	for (int i = 0; i < item->getChildCount(); i++) {
5515 		addItem(items, item->getChild(i));
5516 	}
5517 }
5518 
5519 /// returns pointer to TOC root node
getFlatToc(LVPtrVector<LVTocItem,false> & items)5520 bool LVDocView::getFlatToc(LVPtrVector<LVTocItem, false> & items) {
5521 	items.clear();
5522 	addItem(items, getToc());
5523 	return items.length() > 0;
5524 }
5525 
5526 /// -1 moveto previous page, 1 to next page
moveByPage(int delta)5527 bool LVDocView::moveByPage(int delta) {
5528 	if (m_view_mode == DVM_SCROLL) {
5529 		int p = GetPos();
5530 		SetPos(p + m_dy * delta);
5531 		return GetPos() != p;
5532 	} else {
5533 		int cp = getCurPage();
5534 		int p = cp + delta * getVisiblePageCount();
5535 		goToPage(p);
5536 		return getCurPage() != cp;
5537 	}
5538 }
5539 
5540 /// -1 moveto previous chapter, 0 to current chaoter first pae, 1 to next chapter
moveByChapter(int delta)5541 bool LVDocView::moveByChapter(int delta) {
5542 	/// returns pointer to TOC root node
5543 	LVPtrVector < LVTocItem, false > items;
5544 	if (!getFlatToc(items))
5545 		return false;
5546 	int cp = getCurPage();
5547 	int prevPage = -1;
5548 	int nextPage = -1;
5549     int vcp = getVisiblePageCount();
5550     if (vcp < 1 || vcp > 2)
5551         vcp = 1;
5552 	for (int i = 0; i < items.length(); i++) {
5553 		LVTocItem * item = items[i];
5554 		int p = item->getPage();
5555 		if (p < cp && (prevPage == -1 || prevPage < p))
5556 			prevPage = p;
5557         if (p >= cp + vcp && (nextPage == -1 || nextPage > p))
5558 			nextPage = p;
5559 	}
5560 	if (prevPage < 0)
5561 		prevPage = 0;
5562 	if (nextPage < 0)
5563 		nextPage = getPageCount() - 1;
5564 	int page = delta < 0 ? prevPage : nextPage;
5565 	if (getCurPage() != page) {
5566 		savePosToNavigationHistory();
5567         goToPage(page);
5568 	}
5569 	return true;
5570 }
5571 
5572 /// saves new bookmark
saveRangeBookmark(ldomXRange & range,bmk_type type,lString32 comment)5573 CRBookmark * LVDocView::saveRangeBookmark(ldomXRange & range, bmk_type type,
5574 		lString32 comment) {
5575 	if (range.isNull())
5576 		return NULL;
5577 	if (range.getStart().isNull())
5578 		return NULL;
5579 	CRFileHistRecord * rec = getCurrentFileHistRecord();
5580 	if (!rec)
5581 		return NULL;
5582 	CRBookmark * bmk = new CRBookmark();
5583 	bmk->setType(type);
5584 	bmk->setStartPos(range.getStart().toString());
5585 	if (!range.getEnd().isNull())
5586 		bmk->setEndPos(range.getEnd().toString());
5587 	int p = range.getStart().toPoint().y;
5588 	int fh = m_doc->getFullHeight();
5589 	int percent = fh > 0 ? (int) (p * (lInt64) 10000 / fh) : 0;
5590 	if (percent < 0)
5591 		percent = 0;
5592 	if (percent > 10000)
5593 		percent = 10000;
5594 	bmk->setPercent(percent);
5595 	lString32 postext = range.getRangeText();
5596 	bmk->setPosText(postext);
5597 	bmk->setCommentText(comment);
5598 	bmk->setTitleText(CRBookmark::getChapterName(range.getStart()));
5599 	rec->getBookmarks().add(bmk);
5600         updateBookMarksRanges();
5601 #if 0
5602         if (m_highlightBookmarks && !range.getEnd().isNull())
5603             insertBookmarkPercentInfo(m_pages.FindNearestPage(p, 0),
5604                                   range.getEnd().toPoint().y, percent);
5605 #endif
5606 	return bmk;
5607 }
5608 
5609 /// sets new list of bookmarks, removes old values
setBookmarkList(LVPtrVector<CRBookmark> & bookmarks)5610 void LVDocView::setBookmarkList(LVPtrVector<CRBookmark> & bookmarks)
5611 {
5612     CRFileHistRecord * rec = getCurrentFileHistRecord();
5613     if (!rec)
5614         return;
5615     LVPtrVector<CRBookmark>  & v = rec->getBookmarks();
5616     v.clear();
5617     for (int i=0; i<bookmarks.length(); i++) {
5618         v.add(new CRBookmark(*bookmarks[i]));
5619     }
5620     updateBookMarksRanges();
5621 }
5622 
5623 /// removes bookmark from list, and deletes it, false if not found
removeBookmark(CRBookmark * bm)5624 bool LVDocView::removeBookmark(CRBookmark * bm) {
5625 	CRFileHistRecord * rec = getCurrentFileHistRecord();
5626 	if (!rec)
5627 		return false;
5628 	bm = rec->getBookmarks().remove(bm);
5629 	if (bm) {
5630         updateBookMarksRanges();
5631         delete bm;
5632 		return true;
5633 #if 0
5634             if (m_highlightBookmarks && bm->getType() == bmkt_comment || bm->getType() == bmkt_correction) {
5635                 int by = m_doc->createXPointer(bm->getStartPos()).toPoint().y;
5636                 int page_index = m_pages.FindNearestPage(by, 0);
5637                 bool updateRanges = false;
5638 
5639                 if (page_index > 0 && page_index < m_bookmarksPercents.length()) {
5640                     LVBookMarkPercentInfo *bmi = m_bookmarksPercents[page_index];
5641                     int percent = bm->getPercent();
5642 
5643                     for (int i = 0; bmi != NULL && i < bmi->length(); i++) {
5644                         if ((updateRanges = bmi->get(i) == percent))
5645                             bmi->remove(i);
5646                     }
5647                 }
5648                 if (updateRanges)
5649                     updateBookMarksRanges();
5650             }
5651             delete bm;
5652             return true;
5653 #endif
5654 	} else {
5655 		return false;
5656 	}
5657 }
5658 
5659 #define MAX_EXPORT_BOOKMARKS_SIZE 200000
5660 /// export bookmarks to text file
exportBookmarks(lString32 filename)5661 bool LVDocView::exportBookmarks(lString32 filename) {
5662 	if (m_filename.empty())
5663 		return true; // no document opened
5664 	lChar32 lastChar = filename.lastChar();
5665 	lString32 dir;
5666 	CRLog::trace("exportBookmarks(%s)", UnicodeToUtf8(filename).c_str());
5667 	if (lastChar == '/' || lastChar == '\\') {
5668 		dir = filename;
5669 		CRLog::debug("Creating directory, if not exist %s",
5670 				UnicodeToUtf8(dir).c_str());
5671 		LVCreateDirectory(dir);
5672 		filename.clear();
5673 	}
5674 	if (filename.empty()) {
5675 		CRPropRef props = getDocProps();
5676 		lString32 arcname = props->getStringDef(DOC_PROP_ARC_NAME);
5677 		lString32 arcpath = props->getStringDef(DOC_PROP_ARC_PATH);
5678 		int arcFileCount = props->getIntDef(DOC_PROP_ARC_FILE_COUNT, 0);
5679 		if (!arcpath.empty())
5680 			LVAppendPathDelimiter(arcpath);
5681 		lString32 fname = props->getStringDef(DOC_PROP_FILE_NAME);
5682 		lString32 fpath = props->getStringDef(DOC_PROP_FILE_PATH);
5683 		if (!fpath.empty())
5684 			LVAppendPathDelimiter(fpath);
5685 		if (!arcname.empty()) {
5686 			if (dir.empty())
5687 				dir = arcpath;
5688 			if (arcFileCount > 1)
5689                 filename = arcname + "." + fname + ".bmk.txt";
5690 			else
5691                 filename = arcname + ".bmk.txt";
5692 		} else {
5693 			if (dir.empty())
5694 				dir = fpath;
5695             filename = fname + ".bmk.txt";
5696 		}
5697 		LVAppendPathDelimiter(dir);
5698 		filename = dir + filename;
5699 	}
5700 	CRLog::debug("Exported bookmark filename: %s",
5701 			UnicodeToUtf8(filename).c_str());
5702 	CRFileHistRecord * rec = getCurrentFileHistRecord();
5703 	if (!rec)
5704 		return false;
5705 	// check old content
5706 	lString8 oldContent;
5707 	{
5708 		LVStreamRef is = LVOpenFileStream(filename.c_str(), LVOM_READ);
5709 		if (!is.isNull()) {
5710 			int sz = (int) is->GetSize();
5711 			if (sz > 0 && sz < MAX_EXPORT_BOOKMARKS_SIZE) {
5712 				oldContent.append(sz, ' ');
5713 				lvsize_t bytesRead = 0;
5714 				if (is->Read(oldContent.modify(), sz, &bytesRead) != LVERR_OK
5715 						|| (int) bytesRead != sz)
5716 					oldContent.clear();
5717 			}
5718 		}
5719 	}
5720 	lString8 newContent;
5721 	LVPtrVector < CRBookmark > &bookmarks = rec->getBookmarks();
5722 	for (int i = 0; i < bookmarks.length(); i++) {
5723 		CRBookmark * bmk = bookmarks[i];
5724 		if (bmk->getType() != bmkt_comment && bmk->getType() != bmkt_correction)
5725 			continue;
5726 		if (newContent.empty()) {
5727 			newContent.append(1, (lChar8)0xef);
5728 			newContent.append(1, (lChar8)0xbb);
5729 			newContent.append(1, (lChar8)0xbf);
5730 			newContent << "# Cool Reader 3 - exported bookmarks\r\n";
5731 			newContent << "# file name: " << UnicodeToUtf8(rec->getFileName())
5732 					<< "\r\n";
5733 			if (!rec->getFilePathName().empty())
5734 				newContent << "# file path: " << UnicodeToUtf8(
5735 						rec->getFilePath()) << "\r\n";
5736 			newContent << "# book title: " << UnicodeToUtf8(rec->getTitle())
5737 					<< "\r\n";
5738 			newContent << "# author: " << UnicodeToUtf8(rec->getAuthor())
5739 					<< "\r\n";
5740 			if (!rec->getSeries().empty())
5741 				newContent << "# series: " << UnicodeToUtf8(rec->getSeries())
5742 						<< "\r\n";
5743 			newContent << "\r\n";
5744 		}
5745 		char pos[16];
5746 		int percent = bmk->getPercent();
5747 		lString32 title = bmk->getTitleText();
5748 		sprintf(pos, "%d.%02d%%", percent / 100, percent % 100);
5749 		newContent << "## " << pos << " - "
5750 				<< (bmk->getType() == bmkt_comment ? "comment" : "correction")
5751 				<< "\r\n";
5752 		if (!title.empty())
5753 			newContent << "## " << UnicodeToUtf8(title) << "\r\n";
5754 		if (!bmk->getPosText().empty())
5755 			newContent << "<< " << UnicodeToUtf8(bmk->getPosText()) << "\r\n";
5756 		if (!bmk->getCommentText().empty())
5757 			newContent << ">> " << UnicodeToUtf8(bmk->getCommentText())
5758 					<< "\r\n";
5759 		newContent << "\r\n";
5760 	}
5761 
5762 	if (newContent == oldContent)
5763             return true;
5764 
5765         if (newContent.length() > 0) {
5766             LVStreamRef os = LVOpenFileStream(filename.c_str(), LVOM_WRITE);
5767             if (os.isNull())
5768                     return false;
5769             lvsize_t bytesWritten = 0;
5770 
5771             if (os->Write(newContent.c_str(), newContent.length(), &bytesWritten) != LVERR_OK ||
5772                 bytesWritten != (lUInt32)newContent.length())
5773                 return false;
5774         } else {
5775             LVDeleteFile(filename);
5776             return false;
5777         }
5778 	return true;
5779 }
5780 
5781 /// saves current page bookmark under numbered shortcut
saveCurrentPageShortcutBookmark(int number)5782 CRBookmark * LVDocView::saveCurrentPageShortcutBookmark(int number) {
5783 	CRFileHistRecord * rec = getCurrentFileHistRecord();
5784 	if (!rec)
5785 		return NULL;
5786 	ldomXPointer p = getBookmark();
5787 	if (p.isNull())
5788 		return NULL;
5789 	if (number == 0)
5790 		number = rec->getFirstFreeShortcutBookmark();
5791 	if (number == -1) {
5792 		CRLog::error("Cannot add bookmark: no space left in bookmarks storage.");
5793 		return NULL;
5794 	}
5795 	CRBookmark * bm = rec->setShortcutBookmark(number, p);
5796 	lString32 titleText;
5797 	lString32 posText;
5798 	if (bm && getBookmarkPosText(p, titleText, posText)) {
5799 		bm->setTitleText(titleText);
5800 		bm->setPosText(posText);
5801 		return bm;
5802 	}
5803 	return NULL;
5804 }
5805 
5806 /// saves current page bookmark under numbered shortcut
saveCurrentPageBookmark(lString32 comment)5807 CRBookmark * LVDocView::saveCurrentPageBookmark(lString32 comment) {
5808 	CRFileHistRecord * rec = getCurrentFileHistRecord();
5809 	if (!rec)
5810 		return NULL;
5811 	ldomXPointer p = getBookmark();
5812 	if (p.isNull())
5813 		return NULL;
5814 	CRBookmark * bm = new CRBookmark(p);
5815 	lString32 titleText;
5816 	lString32 posText;
5817 	bm->setType(bmkt_pos);
5818 	if (getBookmarkPosText(p, titleText, posText)) {
5819 		bm->setTitleText(titleText);
5820 		bm->setPosText(posText);
5821 	}
5822 	bm->setStartPos(p.toString());
5823 	int pos = p.toPoint().y;
5824 	int fh = m_doc->getFullHeight();
5825 	int percent = fh > 0 ? (int) (pos * (lInt64) 10000 / fh) : 0;
5826 	if (percent < 0)
5827 		percent = 0;
5828 	if (percent > 10000)
5829 		percent = 10000;
5830 	bm->setPercent(percent);
5831 	bm->setCommentText(comment);
5832 	rec->getBookmarks().add(bm);
5833         updateBookMarksRanges();
5834 	return bm;
5835 }
5836 
5837 /// restores page using bookmark by numbered shortcut
goToPageShortcutBookmark(int number)5838 bool LVDocView::goToPageShortcutBookmark(int number) {
5839 	CRFileHistRecord * rec = getCurrentFileHistRecord();
5840 	if (!rec)
5841 		return false;
5842 	CRBookmark * bmk = rec->getShortcutBookmark(number);
5843 	if (!bmk)
5844 		return false;
5845 	lString32 pos = bmk->getStartPos();
5846 	ldomXPointer p = m_doc->createXPointer(pos);
5847 	if (p.isNull())
5848 		return false;
5849 	if (getCurPage() != getBookmarkPage(p))
5850 		savePosToNavigationHistory();
5851 	goToBookmark(p);
5852         updateBookMarksRanges();
5853 	return true;
5854 }
5855 
myabs(int n)5856 inline int myabs(int n) { return n < 0 ? -n : n; }
5857 
calcBookmarkMatch(lvPoint pt,lvRect & rc1,lvRect & rc2,int type)5858 static int calcBookmarkMatch(lvPoint pt, lvRect & rc1, lvRect & rc2, int type) {
5859     if (pt.y < rc1.top || pt.y >= rc2.bottom)
5860         return -1;
5861     if (type == bmkt_pos) {
5862         return myabs(pt.x - 0);
5863     }
5864     if (rc1.top == rc2.top) {
5865         // single line
5866         if (pt.y >= rc1.top && pt.y < rc2.bottom && pt.x >= rc1.left && pt.x < rc2.right) {
5867             return myabs(pt.x - (rc1.left + rc2.right) / 2);
5868         }
5869         return -1;
5870     } else {
5871         // first line
5872         if (pt.y >= rc1.top && pt.y < rc1.bottom && pt.x >= rc1.left) {
5873             return myabs(pt.x - (rc1.left + rc1.right) / 2);
5874         }
5875         // last line
5876         if (pt.y >= rc2.top && pt.y < rc2.bottom && pt.x < rc2.right) {
5877             return myabs(pt.x - (rc2.left + rc2.right) / 2);
5878         }
5879         // middle line
5880         return myabs(pt.y - (rc1.top + rc2.bottom) / 2);
5881     }
5882 }
5883 
5884 /// find bookmark by window point, return NULL if point doesn't belong to any bookmark
findBookmarkByPoint(lvPoint pt)5885 CRBookmark * LVDocView::findBookmarkByPoint(lvPoint pt) {
5886     CRFileHistRecord * rec = getCurrentFileHistRecord();
5887     if (!rec)
5888         return NULL;
5889     if (!windowToDocPoint(pt))
5890         return NULL;
5891     LVPtrVector<CRBookmark>  & bookmarks = rec->getBookmarks();
5892     CRBookmark * best = NULL;
5893     int bestMatch = -1;
5894     for (int i=0; i<bookmarks.length(); i++) {
5895         CRBookmark * bmk = bookmarks[i];
5896         int t = bmk->getType();
5897         if (t == bmkt_lastpos)
5898             continue;
5899         ldomXPointer p = m_doc->createXPointer(bmk->getStartPos());
5900         if (p.isNull())
5901             continue;
5902         lvRect rc;
5903         if (!p.getRect(rc))
5904             continue;
5905         ldomXPointer ep = (t == bmkt_pos) ? p : m_doc->createXPointer(bmk->getEndPos());
5906         if (ep.isNull())
5907             continue;
5908         lvRect erc;
5909         if (!ep.getRect(erc))
5910             continue;
5911         int match = calcBookmarkMatch(pt, rc, erc, t);
5912         if (match < 0)
5913             continue;
5914         if (match < bestMatch || bestMatch == -1) {
5915             bestMatch = match;
5916             best = bmk;
5917         }
5918     }
5919     return best;
5920 }
5921 
5922 // execute command
doCommand(LVDocCmd cmd,int param)5923 int LVDocView::doCommand(LVDocCmd cmd, int param) {
5924 	CRLog::trace("doCommand(%d, %d)", (int)cmd, param);
5925 	if (NULL == m_doc) {
5926 		CRLog::warn("doCommand(): m_doc is NULL!");
5927 		return 0;
5928 	}
5929 	switch (cmd) {
5930     case DCMD_SET_DOC_FONTS:
5931         CRLog::trace("DCMD_SET_DOC_FONTS(%d)", param);
5932         m_props->setBool(PROP_EMBEDDED_FONTS, (param&1)!=0);
5933         getDocument()->setDocFlag(DOC_FLAG_ENABLE_DOC_FONTS, param!=0);
5934         REQUEST_RENDER("doCommand-set embedded doc fonts")
5935         break;
5936     case DCMD_SET_INTERNAL_STYLES:
5937         CRLog::trace("DCMD_SET_INTERNAL_STYLES(%d)", param);
5938         m_props->setBool(PROP_EMBEDDED_STYLES, (param&1)!=0);
5939         getDocument()->setDocFlag(DOC_FLAG_ENABLE_INTERNAL_STYLES, param!=0);
5940         REQUEST_RENDER("doCommand-set internal styles")
5941         break;
5942     case DCMD_SET_REQUESTED_DOM_VERSION:
5943         CRLog::trace("DCMD_SET_REQUESTED_DOM_VERSION(%d)", param);
5944         m_props->setInt(PROP_REQUESTED_DOM_VERSION, param);
5945         getDocument()->setDOMVersionRequested(param);
5946         REQUEST_RENDER("doCommand-set requested dom version")
5947         break;
5948     case DCMD_RENDER_BLOCK_RENDERING_FLAGS:
5949         CRLog::trace("DCMD_RENDER_BLOCK_RENDERING_FLAGS(%d)", param);
5950         m_props->setInt(PROP_RENDER_BLOCK_RENDERING_FLAGS, param);
5951         getDocument()->setRenderBlockRenderingFlags(param);
5952         REQUEST_RENDER("doCommand-set block rendering flags")
5953         break;
5954     case DCMD_REQUEST_RENDER:
5955         REQUEST_RENDER("doCommand-request render")
5956 		break;
5957 	case DCMD_TOGGLE_BOLD: {
5958 		int b = m_props->getIntDef(PROP_FONT_WEIGHT_EMBOLDEN, 0) ? 0 : 1;
5959 		m_props->setInt(PROP_FONT_WEIGHT_EMBOLDEN, b);
5960 		LVRendSetFontEmbolden(b ? STYLE_FONT_EMBOLD_MODE_EMBOLD
5961 				: STYLE_FONT_EMBOLD_MODE_NORMAL);
5962         REQUEST_RENDER("doCommand-toggle bold")
5963 	}
5964 		break;
5965 	case DCMD_TOGGLE_PAGE_SCROLL_VIEW: {
5966 		toggleViewMode();
5967 	}
5968 		break;
5969 	case DCMD_GO_SCROLL_POS: {
5970 		return goToScrollPos(param);
5971 	}
5972 		break;
5973 	case DCMD_BEGIN: {
5974 		if (getCurPage() > 0) {
5975 			savePosToNavigationHistory();
5976 			return SetPos(0);
5977 		}
5978 	}
5979 		break;
5980 	case DCMD_LINEUP: {
5981 		if (m_view_mode == DVM_SCROLL) {
5982 			return SetPos(GetPos() - param * (m_font_size * 3 / 2));
5983 		} else {
5984 			int p = getCurPage();
5985 			return goToPage(p - getVisiblePageCount());
5986 			//goToPage( m_pages.FindNearestPage(m_pos, -1));
5987 		}
5988 	}
5989 		break;
5990 	case DCMD_PAGEUP: {
5991 		if (param < 1)
5992 			param = 1;
5993 		return moveByPage(-param);
5994 	}
5995 		break;
5996 	case DCMD_PAGEDOWN: {
5997 		if (param < 1)
5998 			param = 1;
5999 		return moveByPage(param);
6000 	}
6001 		break;
6002 	case DCMD_LINK_NEXT: {
6003 		selectNextPageLink(true);
6004 	}
6005 		break;
6006 	case DCMD_LINK_PREV: {
6007 		selectPrevPageLink(true);
6008 	}
6009 		break;
6010 	case DCMD_LINK_FIRST: {
6011 		selectFirstPageLink();
6012 	}
6013 		break;
6014 #if CR_INTERNAL_PAGE_ORIENTATION==1
6015 		case DCMD_ROTATE_BY: // rotate view, param =  +1 - clockwise, -1 - counter-clockwise
6016 		{
6017 			int a = (int)m_rotateAngle;
6018 			if ( param==0 )
6019 			param = 1;
6020 			a = (a + param) & 3;
6021 			SetRotateAngle( (cr_rotate_angle_t)(a) );
6022 		}
6023 		break;
6024 		case DCMD_ROTATE_SET: // rotate viewm param = 0..3 (0=normal, 1=90`, ...)
6025 		{
6026 			SetRotateAngle( (cr_rotate_angle_t)(param & 3 ) );
6027 		}
6028 		break;
6029 #endif
6030 	case DCMD_LINK_GO: {
6031 		goSelectedLink();
6032 	}
6033 		break;
6034 	case DCMD_LINK_BACK: {
6035 		return goBack() ? 1 : 0;
6036 	}
6037 		break;
6038 	case DCMD_LINK_FORWARD: {
6039 		return goForward() ? 1 : 0;
6040 	}
6041 		break;
6042 	case DCMD_LINEDOWN: {
6043 		if (m_view_mode == DVM_SCROLL) {
6044 			return SetPos(GetPos() + param * (m_font_size * 3 / 2));
6045 		} else {
6046 			int p = getCurPage();
6047 			return goToPage(p + getVisiblePageCount());
6048 			//goToPage( m_pages.FindNearestPage(m_pos, +1));
6049 		}
6050 	}
6051 		break;
6052 	case DCMD_END: {
6053 		if (getCurPage() < getPageCount() - getVisiblePageCount()) {
6054 			savePosToNavigationHistory();
6055 			return SetPos(GetFullHeight());
6056 		}
6057 	}
6058 		break;
6059 	case DCMD_GO_POS: {
6060 		if (m_view_mode == DVM_SCROLL) {
6061 			return SetPos(param, true, true);
6062 		} else {
6063 			return goToPage(m_pages.FindNearestPage(param, 0));
6064 		}
6065 	}
6066 		break;
6067 	case DCMD_SCROLL_BY: {
6068 		if (m_view_mode == DVM_SCROLL) {
6069 			CRLog::trace("DCMD_SCROLL_BY %d", param);
6070 			return SetPos(GetPos() + param);
6071 		} else {
6072 			CRLog::trace("DCMD_SCROLL_BY ignored: not in SCROLL mode");
6073 		}
6074 	}
6075 		break;
6076 	case DCMD_GO_PAGE: {
6077 		if (getCurPage() != param) {
6078 			savePosToNavigationHistory();
6079 			return goToPage(param);
6080 		}
6081 	}
6082 		break;
6083     case DCMD_GO_PAGE_DONT_SAVE_HISTORY: {
6084             if (getCurPage() != param) {
6085                 return goToPage(param);
6086             }
6087         }
6088         break;
6089 	case DCMD_ZOOM_IN: {
6090 		ZoomFont(+1);
6091 	}
6092 		break;
6093 	case DCMD_ZOOM_OUT: {
6094 		ZoomFont(-1);
6095 	}
6096 		break;
6097 	case DCMD_TOGGLE_TEXT_FORMAT: {
6098 		if (getTextFormatOptions() == txt_format_auto)
6099 			setTextFormatOptions( txt_format_pre);
6100 		else
6101 			setTextFormatOptions( txt_format_auto);
6102 	}
6103 		break;
6104     case DCMD_SET_TEXT_FORMAT: {
6105         CRLog::trace("DCMD_SET_TEXT_FORMAT(%d)", param);
6106         setTextFormatOptions(param ? txt_format_auto : txt_format_pre);
6107         REQUEST_RENDER("doCommand-set text format")
6108     }
6109         break;
6110     case DCMD_BOOKMARK_SAVE_N: {
6111 		// save current page bookmark under spicified number
6112 		saveCurrentPageShortcutBookmark(param);
6113 	}
6114 		break;
6115 	case DCMD_BOOKMARK_GO_N: {
6116 		// go to bookmark with specified number
6117 		if (!goToPageShortcutBookmark(param)) {
6118 			// if no bookmark exists with specified shortcut, save new bookmark instead
6119 			saveCurrentPageShortcutBookmark(param);
6120 		}
6121 	}
6122 		break;
6123 	case DCMD_MOVE_BY_CHAPTER: {
6124 		return moveByChapter(param);
6125 	}
6126 		break;
6127     case DCMD_SELECT_FIRST_SENTENCE:
6128     case DCMD_SELECT_NEXT_SENTENCE:
6129     case DCMD_SELECT_PREV_SENTENCE:
6130     case DCMD_SELECT_MOVE_LEFT_BOUND_BY_WORDS: // move selection start by words
6131     case DCMD_SELECT_MOVE_RIGHT_BOUND_BY_WORDS: // move selection end by words
6132         return onSelectionCommand( cmd, param );
6133 
6134     /*
6135                 ldomXPointerEx ptr( m_doc->getRootNode(), m_doc->getRootNode()->getChildCount());
6136                 *out << "FORWARD ORDER:\n\n";
6137                 //ptr.nextVisibleText();
6138                 ptr.prevVisibleWordEnd();
6139                 if ( ptr.thisSentenceStart() ) {
6140                     while ( 1 ) {
6141                         ldomXPointerEx ptr2(ptr);
6142                         ptr2.thisSentenceEnd();
6143                         ldomXRange range(ptr, ptr2);
6144                         lString32 str = range.getRangeText();
6145                         *out << ">sentence: " << UnicodeToUtf8(str) << "\n";
6146                         if ( !ptr.nextSentenceStart() )
6147                             break;
6148                     }
6149                 }
6150     */
6151 	default:
6152 		// DO NOTHING
6153 		break;
6154 	}
6155 	return 1;
6156 }
6157 
onSelectionCommand(int cmd,int param)6158 int LVDocView::onSelectionCommand( int cmd, int param )
6159 {
6160     CHECK_RENDER("onSelectionCommand()")
6161     LVRef<ldomXRange> pageRange = getPageDocumentRange();
6162     if (pageRange.isNull()) {
6163         clearSelection();
6164         return 0;
6165     }
6166     ldomXPointerEx pos( getBookmark() );
6167     ldomXRangeList & sel = getDocument()->getSelections();
6168     ldomXRange currSel;
6169     if ( sel.length()>0 )
6170         currSel = *sel[0];
6171     bool moved = false;
6172     bool makeSelStartVisible = true; // true: start, false: end
6173     if ( !currSel.isNull() && cmd == DCMD_SELECT_FIRST_SENTENCE
6174             && !pageRange->isInside(currSel.getStart()) && !pageRange->isInside(currSel.getEnd()) )
6175         currSel.clear();
6176     if ( currSel.isNull() || currSel.getStart().isNull() ) {
6177         // select first sentence on page
6178         if ( pos.isNull() ) {
6179             clearSelection();
6180             return 0;
6181         }
6182         if ( pos.thisSentenceStart() )
6183             currSel.setStart(pos);
6184         moved = true;
6185     }
6186     if ( currSel.getStart().isNull() ) {
6187         clearSelection();
6188         return 0;
6189     }
6190     if (cmd==DCMD_SELECT_MOVE_LEFT_BOUND_BY_WORDS || cmd==DCMD_SELECT_MOVE_RIGHT_BOUND_BY_WORDS) {
6191         if (cmd==DCMD_SELECT_MOVE_RIGHT_BOUND_BY_WORDS)
6192             makeSelStartVisible = false;
6193         int dir = param>0 ? 1 : -1;
6194         int distance = param>0 ? param : -param;
6195         CRLog::debug("Changing selection by words: bound=%s dir=%d distance=%d", (cmd==DCMD_SELECT_MOVE_LEFT_BOUND_BY_WORDS?"left":"right"), dir, distance);
6196         bool res;
6197         if (cmd==DCMD_SELECT_MOVE_LEFT_BOUND_BY_WORDS) {
6198             // DCMD_SELECT_MOVE_LEFT_BOUND_BY_WORDS
6199             for (int i=0; i<distance; i++) {
6200                 if (dir>0) {
6201                     res = currSel.getStart().nextVisibleWordStart();
6202                     CRLog::debug("nextVisibleWordStart returned %s", res?"true":"false");
6203                 } else {
6204                     res = currSel.getStart().prevVisibleWordStart();
6205                     CRLog::debug("prevVisibleWordStart returned %s", res?"true":"false");
6206                 }
6207             }
6208             if (currSel.isNull()) {
6209                 currSel.setEnd(currSel.getStart());
6210                 currSel.getEnd().nextVisibleWordEnd();
6211             }
6212         } else {
6213             // DCMD_SELECT_MOVE_RIGHT_BOUND_BY_WORDS
6214             for (int i=0; i<distance; i++) {
6215                 if (dir>0) {
6216                     res = currSel.getEnd().nextVisibleWordEnd();
6217                     CRLog::debug("nextVisibleWordEnd returned %s", res?"true":"false");
6218                 } else {
6219                     res = currSel.getEnd().prevVisibleWordEnd();
6220                     CRLog::debug("prevVisibleWordEnd returned %s", res?"true":"false");
6221                 }
6222             }
6223             if (currSel.isNull()) {
6224                 currSel.setStart(currSel.getEnd());
6225                 currSel.getStart().prevVisibleWordStart();
6226             }
6227         }
6228         // moved = true; // (never read)
6229     } else {
6230         // selection start doesn't match sentence bounds
6231         if ( !currSel.getStart().isSentenceStart() ) {
6232             CRLog::trace("moving to selection start");
6233             currSel.getStart().thisSentenceStart();
6234             moved = true;
6235         }
6236         // update sentence end
6237         if ( !moved )
6238             switch ( cmd ) {
6239             case DCMD_SELECT_NEXT_SENTENCE:
6240                 if ( !currSel.getStart().nextSentenceStart() ) {
6241                     CRLog::trace("nextSentenceStart() returned false");
6242                     return 0;
6243                 }
6244                 break;
6245             case DCMD_SELECT_PREV_SENTENCE:
6246                 if ( !currSel.getStart().prevSentenceStart() ) {
6247                     CRLog::trace("prevSentenceStart() returned false");
6248                     return 0;
6249                 }
6250                 break;
6251             case DCMD_SELECT_FIRST_SENTENCE:
6252             default: // unknown action
6253                 break;
6254         }
6255         currSel.setEnd(currSel.getStart());
6256         currSel.getEnd().thisSentenceEnd();
6257     }
6258     currSel.setFlags(1);
6259     selectRange(currSel);
6260     lvPoint startPoint = currSel.getStart().toPoint();
6261     lvPoint endPoint = currSel.getEnd().toPoint();
6262     int y0 = GetPos();
6263     int h = m_pageRects[0].height() - m_pageMargins.top
6264             - m_pageMargins.bottom - getPageHeaderHeight();
6265     //int y1 = y0 + h;
6266     if (makeSelStartVisible) {
6267         // make start of selection visible
6268         if (isScrollMode()) {
6269             if (startPoint.y < y0 + m_font_size * 2 || startPoint.y > y0 + h * 3/4)
6270                 SetPos(startPoint.y - m_font_size * 2);
6271         } else {
6272             if (startPoint.y < y0 || startPoint.y >= y0 + h)
6273                 SetPos(startPoint.y);
6274         }
6275         //goToBookmark(currSel.getStart());
6276     } else {
6277         // make end of selection visible
6278         if (isScrollMode()) {
6279             if (endPoint.y > y0 + h * 3/4 - m_font_size * 2)
6280                 SetPos(endPoint.y - h * 3/4 + m_font_size * 2, false);
6281         } else {
6282             if (endPoint.y < y0 || endPoint.y >= y0 + h)
6283                 SetPos(endPoint.y, false);
6284         }
6285     }
6286     CRLog::debug("Sel: %s", LCSTR(currSel.getRangeText()));
6287     return 1;
6288 }
6289 
6290 //static int cr_font_sizes[] = { 24, 29, 33, 39, 44 };
6291 static int cr_interline_spaces[] = { 100, 70, 75, 80, 85, 90, 95, 100, 105, 110, 115, 120, 125, 130, 135, 140, 145, 150, 160, 180, 200 };
6292 
6293 static const char * def_style_macros[] = {
6294     "styles.def.align", "text-align: justify",
6295     "styles.def.text-indent", "text-indent: 1.2em",
6296     "styles.def.margin-top", "margin-top: 0em",
6297     "styles.def.margin-bottom", "margin-bottom: 0em",
6298     "styles.def.margin-left", "margin-left: 0em",
6299     "styles.def.margin-right", "margin-right: 0em",
6300     "styles.title.align", "text-align: center",
6301     "styles.title.text-indent", "text-indent: 0em",
6302     "styles.title.margin-top", "margin-top: 0.3em",
6303     "styles.title.margin-bottom", "margin-bottom: 0.3em",
6304     "styles.title.margin-left", "margin-left: 0em",
6305     "styles.title.margin-right", "margin-right: 0em",
6306     "styles.title.font-size", "font-size: 110%",
6307     "styles.title.font-weight", "font-weight: bolder",
6308     "styles.subtitle.align", "text-align: center",
6309     "styles.subtitle.text-indent", "text-indent: 0em",
6310     "styles.subtitle.margin-top", "margin-top: 0.2em",
6311     "styles.subtitle.margin-bottom", "margin-bottom: 0.2em",
6312     "styles.subtitle.font-style", "font-style: italic",
6313     "styles.cite.align", "text-align: justify",
6314     "styles.cite.text-indent", "text-indent: 1.2em",
6315     "styles.cite.margin-top", "margin-top: 0.3em",
6316     "styles.cite.margin-bottom", "margin-bottom: 0.3em",
6317     "styles.cite.margin-left", "margin-left: 1em",
6318     "styles.cite.margin-right", "margin-right: 1em",
6319     "styles.cite.font-style", "font-style: italic",
6320     "styles.epigraph.align", "text-align: left",
6321     "styles.epigraph.text-indent", "text-indent: 1.2em",
6322     "styles.epigraph.margin-top", "margin-top: 0.3em",
6323     "styles.epigraph.margin-bottom", "margin-bottom: 0.3em",
6324     "styles.epigraph.margin-left", "margin-left: 15%",
6325     "styles.epigraph.margin-right", "margin-right: 1em",
6326     "styles.epigraph.font-style", "font-style: italic",
6327     "styles.pre.align", "text-align: left",
6328     "styles.pre.text-indent", "text-indent: 0em",
6329     "styles.pre.margin-top", "margin-top: 0em",
6330     "styles.pre.margin-bottom", "margin-bottom: 0em",
6331     "styles.pre.margin-left", "margin-left: 0em",
6332     "styles.pre.margin-right", "margin-right: 0em",
6333     "styles.pre.font-face", "font-family: \"Courier New\", \"Courier\", monospace",
6334     "styles.poem.align", "text-align: left",
6335     "styles.poem.text-indent", "text-indent: 0em",
6336     "styles.poem.margin-top", "margin-top: 0.3em",
6337     "styles.poem.margin-bottom", "margin-bottom: 0.3em",
6338     "styles.poem.margin-left", "margin-left: 15%",
6339     "styles.poem.margin-right", "margin-right: 1em",
6340     "styles.poem.font-style", "font-style: italic",
6341     "styles.text-author.font-style", "font-style: italic",
6342     "styles.text-author.font-weight", "font-weight: bolder",
6343     "styles.text-author.margin-left", "margin-left: 1em",
6344     "styles.text-author.font-style", "font-style: italic",
6345     "styles.text-author.font-weight", "font-weight: bolder",
6346     "styles.text-author.margin-left", "margin-left: 1em",
6347     "styles.link.text-decoration", "text-decoration: underline",
6348     "styles.footnote-link.font-size", "font-size: 70%",
6349     "styles.footnote-link.vertical-align", "vertical-align: super",
6350     "styles.footnote", "font-size: 70%",
6351     "styles.footnote-title", "font-size: 110%",
6352     "styles.annotation.font-size", "font-size: 90%",
6353     "styles.annotation.margin-left", "margin-left: 1em",
6354     "styles.annotation.margin-right", "margin-right: 1em",
6355     "styles.annotation.font-style", "font-style: italic",
6356     "styles.annotation.align", "text-align: justify",
6357     "styles.annotation.text-indent", "text-indent: 1.2em",
6358     NULL,
6359     NULL,
6360 };
6361 
6362 /// sets default property values if properties not found, checks ranges
propsUpdateDefaults(CRPropRef props)6363 void LVDocView::propsUpdateDefaults(CRPropRef props) {
6364 	lString32Collection list;
6365 	fontMan->getFaceList(list);
6366 	static int def_aa_props[] = { 2, 1, 0 };
6367 
6368 	props->setIntDef(PROP_MIN_FILE_SIZE_TO_CACHE,
6369             300000); // ~6M
6370 	props->setIntDef(PROP_FORCED_MIN_FILE_SIZE_TO_CACHE,
6371 			DOCUMENT_CACHING_MIN_SIZE); // 32K
6372 	props->setIntDef(PROP_PROGRESS_SHOW_FIRST_PAGE, 1);
6373 
6374 	props->limitValueList(PROP_FONT_ANTIALIASING, def_aa_props,
6375 			sizeof(def_aa_props) / sizeof(int));
6376 	props->setHexDef(PROP_FONT_COLOR, 0x000000);
6377 	props->setHexDef(PROP_BACKGROUND_COLOR, 0xFFFFFF);
6378 	props->setHexDef(PROP_STATUS_FONT_COLOR, 0xFF000000);
6379 //	props->setIntDef(PROP_TXT_OPTION_PREFORMATTED, 0);
6380 	props->setIntDef(PROP_AUTOSAVE_BOOKMARKS, 1);
6381 	props->setIntDef(PROP_DISPLAY_FULL_UPDATE_INTERVAL, 1);
6382 	props->setIntDef(PROP_DISPLAY_TURBO_UPDATE_MODE, 0);
6383 
6384 	lString8 defFontFace;
6385 	static const char * goodFonts[] = { "DejaVu Sans", "FreeSans",
6386 			"Liberation Sans", "Arial", "Verdana", NULL };
6387 	static const char * fallbackFonts = "Droid Sans Fallback; Noto Sans CJK SC; Noto Sans Arabic UI; Noto Sans Devanagari UI; FreeSans; FreeSerif; Noto Serif; Noto Sans; Arial Unicode MS";
6388 	for (int i = 0; goodFonts[i]; i++) {
6389 		if (list.contains(lString32(goodFonts[i]))) {
6390 			defFontFace = lString8(goodFonts[i]);
6391 			break;
6392 		}
6393 	}
6394 	if (defFontFace.empty())
6395 		defFontFace = UnicodeToUtf8(list[0]);
6396 
6397 	lString8 defStatusFontFace(DEFAULT_STATUS_FONT_NAME);
6398 	props->setStringDef(PROP_FONT_FACE, defFontFace.c_str());
6399 	props->setStringDef(PROP_STATUS_FONT_FACE, defStatusFontFace.c_str());
6400 	if (list.length() > 0 && !list.contains(props->getStringDef(PROP_FONT_FACE,
6401 			defFontFace.c_str())))
6402 		props->setString(PROP_FONT_FACE, list[0]);
6403 	props->setStringDef(PROP_FALLBACK_FONT_FACES, fallbackFonts);
6404 
6405 #if USE_LIMITED_FONT_SIZES_SET
6406 	props->setIntDef(PROP_FONT_SIZE,
6407 			m_font_sizes[m_font_sizes.length() * 2 / 3]);
6408 	props->limitValueList(PROP_FONT_SIZE, m_font_sizes.ptr(),
6409 			m_font_sizes.length());
6410 #else
6411 	props->setIntDef(PROP_FONT_SIZE, m_min_font_size + (m_min_font_size + m_max_font_size)/7);
6412 #endif
6413 	props->limitValueList(PROP_INTERLINE_SPACE, cr_interline_spaces,
6414 			sizeof(cr_interline_spaces) / sizeof(int));
6415 #if CR_INTERNAL_PAGE_ORIENTATION==1
6416 	static int def_rot_angle[] = {0, 1, 2, 3};
6417 	props->limitValueList( PROP_ROTATE_ANGLE, def_rot_angle, 4 );
6418 #endif
6419 	static int bool_options_def_true[] = { 1, 0 };
6420 	static int bool_options_def_false[] = { 0, 1 };
6421 
6422 	props->limitValueList(PROP_FONT_WEIGHT_EMBOLDEN, bool_options_def_false, 2);
6423 #ifndef ANDROID
6424 	props->limitValueList(PROP_EMBEDDED_STYLES, bool_options_def_true, 2);
6425 	props->limitValueList(PROP_EMBEDDED_FONTS, bool_options_def_true, 2);
6426 #endif
6427 	static int int_option_hinting[] = { 0, 1, 2 };
6428 	props->limitValueList(PROP_FONT_HINTING, int_option_hinting, 3);
6429     static int int_option_shaping[] = { 1, 0, 2 };
6430     props->limitValueList(PROP_FONT_SHAPING, int_option_shaping, 3);
6431     static int int_options_1_2[] = { 2, 1 };
6432 	props->limitValueList(PROP_LANDSCAPE_PAGES, int_options_1_2, 2);
6433 	props->limitValueList(PROP_PAGE_VIEW_MODE, bool_options_def_true, 2);
6434 	props->limitValueList(PROP_FOOTNOTES, bool_options_def_true, 2);
6435 	props->limitValueList(PROP_SHOW_TIME, bool_options_def_false, 2);
6436 	props->limitValueList(PROP_DISPLAY_INVERSE, bool_options_def_false, 2);
6437 	props->limitValueList(PROP_BOOKMARK_ICONS, bool_options_def_false, 2);
6438     props->limitValueList(PROP_FONT_KERNING_ENABLED, bool_options_def_false, 2);
6439     //props->limitValueList(PROP_FLOATING_PUNCTUATION, bool_options_def_true, 2);
6440     static int def_bookmark_highlight_modes[] = { 0, 1, 2 };
6441     props->setIntDef(PROP_HIGHLIGHT_COMMENT_BOOKMARKS, highlight_mode_underline);
6442     props->limitValueList(PROP_HIGHLIGHT_COMMENT_BOOKMARKS, def_bookmark_highlight_modes, sizeof(def_bookmark_highlight_modes)/sizeof(int));
6443     props->setColorDef(PROP_HIGHLIGHT_SELECTION_COLOR, 0xC0C0C0); // silver
6444     props->setColorDef(PROP_HIGHLIGHT_BOOKMARK_COLOR_COMMENT, 0xA08020); // yellow
6445     props->setColorDef(PROP_HIGHLIGHT_BOOKMARK_COLOR_CORRECTION, 0xA04040); // red
6446 
6447     static int def_status_line[] = { 0, 1, 2 };
6448 	props->limitValueList(PROP_STATUS_LINE, def_status_line, 3);
6449     static int def_margin[] = {8, 0, 1, 2, 3, 4, 5, 8, 9, 10, 11, 12, 13, 14, 15, 16, 20, 25, 30, 40, 50, 60, 80, 100, 130, 150, 200, 300};
6450 	props->limitValueList(PROP_PAGE_MARGIN_TOP, def_margin, sizeof(def_margin)/sizeof(int));
6451 	props->limitValueList(PROP_PAGE_MARGIN_BOTTOM, def_margin, sizeof(def_margin)/sizeof(int));
6452 	props->limitValueList(PROP_PAGE_MARGIN_LEFT, def_margin, sizeof(def_margin)/sizeof(int));
6453 	props->limitValueList(PROP_PAGE_MARGIN_RIGHT, def_margin, sizeof(def_margin)/sizeof(int));
6454     static int def_rounded_corners_margin[] = {0, 5, 10, 15, 20, 30, 40, 50, 60, 70,80, 90, 100, 120, 140, 160};
6455     props->limitValueList(PROP_ROUNDED_CORNERS_MARGIN, def_rounded_corners_margin, sizeof(def_rounded_corners_margin)/sizeof(int));
6456 
6457     static int def_updates[] = { 1, 0, 2, 3, 4, 5, 6, 7, 8, 10, 14 };
6458 	props->limitValueList(PROP_DISPLAY_FULL_UPDATE_INTERVAL, def_updates, 11);
6459 	int fs = props->getIntDef(PROP_STATUS_FONT_SIZE, INFO_FONT_SIZE);
6460     if (fs < MIN_STATUS_FONT_SIZE)
6461         fs = MIN_STATUS_FONT_SIZE;
6462     else if (fs > MAX_STATUS_FONT_SIZE)
6463         fs = MAX_STATUS_FONT_SIZE;
6464 	props->setIntDef(PROP_STATUS_FONT_SIZE, fs);
6465     props->limitValueList(PROP_TEXTLANG_EMBEDDED_LANGS_ENABLED, bool_options_def_false, 2);
6466     props->limitValueList(PROP_TEXTLANG_HYPHENATION_ENABLED, bool_options_def_true, 2);
6467 	lString32 hyph = props->getStringDef(PROP_HYPHENATION_DICT,
6468 			DEF_HYPHENATION_DICT);
6469 	HyphDictionaryList * dictlist = HyphMan::getDictList();
6470 	if (dictlist) {
6471 		if (dictlist->find(hyph))
6472 			props->setStringDef(PROP_HYPHENATION_DICT, hyph);
6473 		else
6474 			props->setStringDef(PROP_HYPHENATION_DICT, lString32(
6475 					HYPH_DICT_ID_ALGORITHM));
6476 	}
6477 	props->setIntDef(PROP_STATUS_LINE, 1);
6478 	props->setIntDef(PROP_SHOW_TITLE, 1);
6479 	props->setIntDef(PROP_SHOW_TIME, 1);
6480 	props->setIntDef(PROP_SHOW_BATTERY, 1);
6481     props->setIntDef(PROP_SHOW_BATTERY_PERCENT, 0);
6482     props->setIntDef(PROP_SHOW_PAGE_COUNT, 1);
6483     props->setIntDef(PROP_SHOW_PAGE_NUMBER, 1);
6484     props->setIntDef(PROP_SHOW_POS_PERCENT, 0);
6485     props->setIntDef(PROP_STATUS_CHAPTER_MARKS, 1);
6486     props->setIntDef(PROP_FLOATING_PUNCTUATION, 1);
6487 
6488 #ifndef ANDROID
6489     props->setIntDef(PROP_EMBEDDED_STYLES, 1);
6490     props->setIntDef(PROP_EMBEDDED_FONTS, 1);
6491     props->setIntDef(PROP_TXT_OPTION_PREFORMATTED, 0);
6492     props->limitValueList(PROP_TXT_OPTION_PREFORMATTED, bool_options_def_false,
6493             2);
6494 #endif
6495 
6496     props->setStringDef(PROP_FONT_GAMMA, "1.00");
6497 
6498     img_scaling_option_t defImgScaling;
6499     props->setIntDef(PROP_IMG_SCALING_ZOOMOUT_BLOCK_SCALE, defImgScaling.max_scale);
6500     props->setIntDef(PROP_IMG_SCALING_ZOOMOUT_INLINE_SCALE, 0); //auto
6501     props->setIntDef(PROP_IMG_SCALING_ZOOMIN_BLOCK_SCALE, defImgScaling.max_scale);
6502     props->setIntDef(PROP_IMG_SCALING_ZOOMIN_INLINE_SCALE, 0); // auto
6503     props->setIntDef(PROP_IMG_SCALING_ZOOMOUT_BLOCK_MODE, defImgScaling.mode);
6504     props->setIntDef(PROP_IMG_SCALING_ZOOMOUT_INLINE_MODE, defImgScaling.mode);
6505     props->setIntDef(PROP_IMG_SCALING_ZOOMIN_BLOCK_MODE, defImgScaling.mode);
6506     props->setIntDef(PROP_IMG_SCALING_ZOOMIN_INLINE_MODE, defImgScaling.mode);
6507 
6508     int p = props->getIntDef(PROP_FORMAT_SPACE_WIDTH_SCALE_PERCENT, DEF_SPACE_WIDTH_SCALE_PERCENT);
6509     if (p<10)
6510         p = 10;
6511     if (p>500)
6512         p = 500;
6513     props->setInt(PROP_FORMAT_SPACE_WIDTH_SCALE_PERCENT, p);
6514 
6515     p = props->getIntDef(PROP_FORMAT_MIN_SPACE_CONDENSING_PERCENT, DEF_MIN_SPACE_CONDENSING_PERCENT);
6516     if (p<25)
6517         p = 25;
6518     if (p>100)
6519         p = 100;
6520     props->setInt(PROP_FORMAT_MIN_SPACE_CONDENSING_PERCENT, p);
6521 
6522     p = props->getIntDef(PROP_FORMAT_UNUSED_SPACE_THRESHOLD_PERCENT, DEF_UNUSED_SPACE_THRESHOLD_PERCENT);
6523     if (p<0)
6524         p = 0;
6525     if (p>20)
6526         p = 20;
6527     props->setInt(PROP_FORMAT_UNUSED_SPACE_THRESHOLD_PERCENT, p);
6528 
6529     p = props->getIntDef(PROP_FORMAT_MAX_ADDED_LETTER_SPACING_PERCENT, DEF_MAX_ADDED_LETTER_SPACING_PERCENT);
6530     if (p<0)
6531         p = 0;
6532     if (p>20)
6533         p = 20;
6534     props->setInt(PROP_FORMAT_MAX_ADDED_LETTER_SPACING_PERCENT, p);
6535 
6536 #ifndef ANDROID
6537     props->setIntDef(PROP_RENDER_DPI, DEF_RENDER_DPI); // 96 dpi
6538     props->setIntDef(PROP_RENDER_SCALE_FONT_WITH_DPI, DEF_RENDER_SCALE_FONT_WITH_DPI); // no scale
6539     props->setIntDef(PROP_RENDER_BLOCK_RENDERING_FLAGS, BLOCK_RENDERING_FLAGS_DEFAULT);
6540 #endif
6541 
6542     props->setIntDef(PROP_FILE_PROPS_FONT_SIZE, 22);
6543 
6544     for (int i=0; def_style_macros[i*2]; i++)
6545         props->setStringDef(def_style_macros[i * 2], def_style_macros[i * 2 + 1]);
6546 }
6547 
setStatusMode(int pos,bool showClock,bool showTitle,bool showBattery,bool showChapterMarks,bool showPercent,bool showPageNumber,bool showPageCount)6548 void LVDocView::setStatusMode(int pos, bool showClock, bool showTitle,
6549         bool showBattery, bool showChapterMarks, bool showPercent, bool showPageNumber, bool showPageCount) {
6550 	CRLog::debug("LVDocView::setStatusMode(%d, %s %s %s %s)", pos,
6551 			showClock ? "clock" : "", showTitle ? "title" : "",
6552 			showBattery ? "battery" : "", showChapterMarks ? "marks" : "");
6553     setPageHeaderPosition(pos);
6554     setPageHeaderInfo(
6555         (showPageNumber ? PGHDR_PAGE_NUMBER : 0)
6556         | (showClock ? PGHDR_CLOCK : 0)
6557         | (showBattery ? PGHDR_BATTERY : 0)
6558         | (showPageCount ? PGHDR_PAGE_COUNT : 0)
6559         | (showTitle ? PGHDR_AUTHOR : 0)
6560         | (showTitle ? PGHDR_TITLE : 0)
6561         | (showChapterMarks ? PGHDR_CHAPTER_MARKS : 0)
6562         | (showPercent ? PGHDR_PERCENT : 0)
6563         //| PGHDR_CLOCK
6564     );
6565 }
6566 
propApply(lString8 name,lString32 value)6567 bool LVDocView::propApply(lString8 name, lString32 value) {
6568     CRPropRef props = LVCreatePropsContainer();
6569     props->setString(name.c_str(), value);
6570     CRPropRef unknown = propsApply( props );
6571     return ( 0 == unknown->getCount() );
6572 }
6573 
6574 /// applies properties, returns list of not recognized properties
propsApply(CRPropRef props)6575 CRPropRef LVDocView::propsApply(CRPropRef props) {
6576     CRLog::trace("LVDocView::propsApply( %d items )", props->getCount());
6577     CRPropRef unknown = LVCreatePropsContainer();
6578     bool needUpdateMargins = false;
6579     bool needUpdateHyphenation = false;
6580     for (int i = 0; i < props->getCount(); i++) {
6581         lString8 name(props->getName(i));
6582         lString32 value = props->getValue(i);
6583         if (name == PROP_FONT_ANTIALIASING) {
6584             int antialiasingMode = props->getIntDef(PROP_FONT_ANTIALIASING, 2);
6585             fontMan->SetAntialiasMode(antialiasingMode);
6586             REQUEST_RENDER("propsApply - font antialiasing")
6587         } else if (name.startsWith(cs8("styles."))) {
6588             REQUEST_RENDER("propsApply - styles.*")
6589         } else if (name == PROP_FONT_GAMMA) {
6590             double gamma = 1.0;
6591             lString32 s = props->getStringDef(PROP_FONT_GAMMA, "1.0");
6592             lString8 s8 = UnicodeToUtf8(s);
6593             if ( sscanf(s8.c_str(), "%lf", &gamma)==1 ) {
6594                 fontMan->SetGamma(gamma);
6595                 clearImageCache();
6596             }
6597         } else if (name == PROP_FONT_HINTING) {
6598             int mode = props->getIntDef(PROP_FONT_HINTING, (int)HINTING_MODE_AUTOHINT);
6599             if (mode >= HINTING_MODE_DISABLED && mode <= HINTING_MODE_AUTOHINT) {
6600                 //CRLog::debug("Setting hinting mode to %d", mode);
6601                 fontMan->SetHintingMode((hinting_mode_t)mode);
6602                 REQUEST_RENDER("propsApply - font hinting")
6603                     // requestRender() does m_doc->clearRendBlockCache(), which is needed
6604                     // on hinting mode change
6605             }
6606         } else if (name == PROP_HIGHLIGHT_SELECTION_COLOR || name == PROP_HIGHLIGHT_BOOKMARK_COLOR_COMMENT || name == PROP_HIGHLIGHT_BOOKMARK_COLOR_COMMENT) {
6607             REQUEST_RENDER("propsApply - highlight")
6608         } else if (name == PROP_LANDSCAPE_PAGES) {
6609             int pages = props->getIntDef(PROP_LANDSCAPE_PAGES, 2);
6610             setVisiblePageCount(pages);
6611         } else if (name == PROP_FONT_KERNING_ENABLED) {
6612             bool kerning = props->getBoolDef(PROP_FONT_KERNING_ENABLED, false);
6613             fontMan->SetKerning(kerning);
6614             REQUEST_RENDER("propsApply - kerning")
6615         } else if (name == PROP_FONT_SHAPING) {
6616             int mode = props->getIntDef(PROP_FONT_SHAPING, (int)SHAPING_MODE_HARFBUZZ_LIGHT);
6617             if (mode >= SHAPING_MODE_FREETYPE && mode <= SHAPING_MODE_HARFBUZZ) {
6618                 fontMan->SetShapingMode((shaping_mode_t)mode);
6619                 REQUEST_RENDER("Setting shaping mode");
6620             }
6621         } else if (name == PROP_FONT_WEIGHT_EMBOLDEN) {
6622             bool embolden = props->getBoolDef(PROP_FONT_WEIGHT_EMBOLDEN, false);
6623             int v = embolden ? STYLE_FONT_EMBOLD_MODE_EMBOLD
6624                              : STYLE_FONT_EMBOLD_MODE_NORMAL;
6625             if (v != LVRendGetFontEmbolden()) {
6626                 LVRendSetFontEmbolden(v);
6627                 REQUEST_RENDER("propsApply - embolden")
6628             }
6629         } else if (name == PROP_TXT_OPTION_PREFORMATTED) {
6630             bool preformatted = props->getBoolDef(PROP_TXT_OPTION_PREFORMATTED,
6631                                                   false);
6632             setTextFormatOptions(preformatted ? txt_format_pre
6633                                               : txt_format_auto);
6634         } else if (name == PROP_IMG_SCALING_ZOOMIN_INLINE_SCALE || name == PROP_IMG_SCALING_ZOOMIN_INLINE_MODE
6635                    || name == PROP_IMG_SCALING_ZOOMOUT_INLINE_SCALE || name == PROP_IMG_SCALING_ZOOMOUT_INLINE_MODE
6636                    || name == PROP_IMG_SCALING_ZOOMIN_BLOCK_SCALE || name == PROP_IMG_SCALING_ZOOMIN_BLOCK_MODE
6637                    || name == PROP_IMG_SCALING_ZOOMOUT_BLOCK_SCALE || name == PROP_IMG_SCALING_ZOOMOUT_BLOCK_MODE
6638                    ) {
6639             m_props->setString(name.c_str(), value);
6640             REQUEST_RENDER("propsApply -img scale")
6641         } else if (name == PROP_FONT_COLOR || name == PROP_BACKGROUND_COLOR
6642                    || name == PROP_DISPLAY_INVERSE || name==PROP_STATUS_FONT_COLOR) {
6643             // update current value in properties
6644             m_props->setString(name.c_str(), value);
6645             lUInt32 textColor = props->getColorDef(PROP_FONT_COLOR, m_props->getColorDef(PROP_FONT_COLOR, 0x000000));
6646             lUInt32 backColor = props->getColorDef(PROP_BACKGROUND_COLOR,
6647                                                    m_props->getIntDef(PROP_BACKGROUND_COLOR,
6648                                                                       0xFFFFFF));
6649             lUInt32 statusColor = props->getColorDef(PROP_STATUS_FONT_COLOR,
6650                                                      m_props->getColorDef(PROP_STATUS_FONT_COLOR,
6651                                                                           0xFF000000));
6652             bool inverse = props->getBoolDef(PROP_DISPLAY_INVERSE, m_props->getBoolDef(PROP_DISPLAY_INVERSE, false));
6653             if (inverse) {
6654                 CRLog::trace("Setting inverse colors");
6655                 //if (name == PROP_FONT_COLOR)
6656                 setBackgroundColor(textColor);
6657                 //if (name == PROP_BACKGROUND_COLOR)
6658                 setTextColor(backColor);
6659                 //if (name == PROP_BACKGROUND_COLOR)
6660                 setStatusColor(backColor);
6661                 REQUEST_RENDER("propsApply  color") // TODO: only colors to be changed
6662             } else {
6663                 CRLog::trace("Setting normal colors");
6664                 //if (name == PROP_BACKGROUND_COLOR)
6665                 setBackgroundColor(backColor);
6666                 //if (name == PROP_FONT_COLOR)
6667                 setTextColor(textColor);
6668                 //if (name == PROP_STATUS_FONT_COLOR)
6669                 setStatusColor(statusColor);
6670                 REQUEST_RENDER("propsApply  color") // TODO: only colors to be changed
6671             }
6672         } else if (name == PROP_PAGE_MARGIN_TOP || name
6673                    == PROP_PAGE_MARGIN_LEFT || name == PROP_PAGE_MARGIN_RIGHT
6674                    || name == PROP_PAGE_MARGIN_BOTTOM) {
6675             needUpdateMargins = true;
6676         } else if (name == PROP_FONT_FACE) {
6677             setDefaultFontFace(UnicodeToUtf8(value));
6678             needUpdateMargins = true;
6679         } else if (name == PROP_FALLBACK_FONT_FACES) {
6680             lString8 oldFaces = fontMan->GetFallbackFontFaces();
6681             if ( UnicodeToUtf8(value)!=oldFaces )
6682                 fontMan->SetFallbackFontFaces(UnicodeToUtf8(value));
6683             value = Utf8ToUnicode(fontMan->GetFallbackFontFaces());
6684             if ( UnicodeToUtf8(value) != oldFaces ) {
6685                 REQUEST_RENDER("propsApply  fallback font faces")
6686             }
6687         } else if (name == PROP_STATUS_FONT_FACE) {
6688             setStatusFontFace(UnicodeToUtf8(value));
6689         } else if (name == PROP_STATUS_LINE || name == PROP_SHOW_TIME
6690                    || name	== PROP_SHOW_TITLE || name == PROP_SHOW_BATTERY
6691                    || name == PROP_STATUS_CHAPTER_MARKS || name == PROP_SHOW_POS_PERCENT
6692                    || name == PROP_SHOW_PAGE_COUNT || name == PROP_SHOW_PAGE_NUMBER) {
6693             m_props->setString(name.c_str(), value);
6694             setStatusMode(m_props->getIntDef(PROP_STATUS_LINE, 1),
6695                           m_props->getBoolDef(PROP_SHOW_TIME, false),
6696                           m_props->getBoolDef(PROP_SHOW_TITLE, true),
6697                           m_props->getBoolDef(PROP_SHOW_BATTERY, true),
6698                           m_props->getBoolDef(PROP_STATUS_CHAPTER_MARKS, true),
6699                           m_props->getBoolDef(PROP_SHOW_POS_PERCENT, false),
6700                           m_props->getBoolDef(PROP_SHOW_PAGE_NUMBER, true),
6701                           m_props->getBoolDef(PROP_SHOW_PAGE_COUNT, true)
6702                           );
6703             //} else if ( name==PROP_BOOKMARK_ICONS ) {
6704             //    enableBookmarkIcons( value==U"1" );
6705         } else if (name == PROP_FONT_SIZE) {
6706 #if USE_LIMITED_FONT_SIZES_SET
6707             int fontSize = props->getIntDef(PROP_FONT_SIZE, m_font_sizes[0]);
6708 #else
6709             int fontSize = props->getIntDef(PROP_FONT_SIZE, m_min_font_size);
6710 #endif
6711             setFontSize(fontSize);//cr_font_sizes
6712             value = lString32::itoa(m_requested_font_size);
6713             updatePageMargins();
6714         } else if (name == PROP_STATUS_FONT_SIZE) {
6715             int fontSize = props->getIntDef(PROP_STATUS_FONT_SIZE,
6716                                             INFO_FONT_SIZE);
6717             if (fontSize < MIN_STATUS_FONT_SIZE)
6718                 fontSize = MIN_STATUS_FONT_SIZE;
6719             else if (fontSize > MAX_STATUS_FONT_SIZE)
6720                 fontSize = MAX_STATUS_FONT_SIZE;
6721             setStatusFontSize(fontSize);//cr_font_sizes
6722             value = lString32::itoa(fontSize);
6723         } else if (name == PROP_HYPHENATION_DICT) {
6724             needUpdateHyphenation = true;
6725         } else if (name == PROP_HYPHENATION_LEFT_HYPHEN_MIN) {
6726             int leftHyphenMin = props->getIntDef(PROP_HYPHENATION_LEFT_HYPHEN_MIN, HYPH_DEFAULT_HYPHEN_MIN);
6727             if (HyphMan::getLeftHyphenMin() != leftHyphenMin) {
6728                 HyphMan::setLeftHyphenMin(leftHyphenMin);
6729                 REQUEST_RENDER("propsApply hyphenation left_hyphen_min")
6730             }
6731         } else if (name == PROP_HYPHENATION_RIGHT_HYPHEN_MIN) {
6732             int rightHyphenMin = props->getIntDef(PROP_HYPHENATION_RIGHT_HYPHEN_MIN, HYPH_DEFAULT_HYPHEN_MIN);
6733             if (HyphMan::getRightHyphenMin() != rightHyphenMin) {
6734                 HyphMan::setRightHyphenMin(rightHyphenMin);
6735                 REQUEST_RENDER("propsApply hyphenation right_hyphen_min")
6736             }
6737         } else if (name == PROP_HYPHENATION_TRUST_SOFT_HYPHENS) {
6738             int trustSoftHyphens = props->getIntDef(PROP_HYPHENATION_TRUST_SOFT_HYPHENS, HYPH_DEFAULT_TRUST_SOFT_HYPHENS);
6739             if (HyphMan::getTrustSoftHyphens() != trustSoftHyphens) {
6740                 HyphMan::setTrustSoftHyphens(trustSoftHyphens);
6741                 REQUEST_RENDER("propsApply hyphenation trust_soft_hyphens")
6742             }
6743         } else if (name == PROP_TEXTLANG_MAIN_LANG) {
6744             needUpdateHyphenation = true;
6745         } else if (name == PROP_TEXTLANG_EMBEDDED_LANGS_ENABLED) {
6746             needUpdateHyphenation = true;
6747         } else if (name == PROP_TEXTLANG_HYPHENATION_ENABLED) {
6748             needUpdateHyphenation = true;
6749         } else if (name == PROP_TEXTLANG_HYPH_SOFT_HYPHENS_ONLY) {
6750             bool enabled = props->getIntDef(PROP_TEXTLANG_HYPH_SOFT_HYPHENS_ONLY, TEXTLANG_DEFAULT_HYPH_SOFT_HYPHENS_ONLY);
6751             if ( enabled != TextLangMan::getHyphenationSoftHyphensOnly() ) {
6752                 TextLangMan::setHyphenationSoftHyphensOnly( enabled );
6753                 REQUEST_RENDER("propsApply textlang hyphenation_soft_hyphens_only")
6754             }
6755         } else if (name == PROP_TEXTLANG_HYPH_FORCE_ALGORITHMIC) {
6756             bool enabled = props->getIntDef(PROP_TEXTLANG_HYPH_FORCE_ALGORITHMIC, TEXTLANG_DEFAULT_HYPH_FORCE_ALGORITHMIC);
6757             if ( enabled != TextLangMan::getHyphenationForceAlgorithmic() ) {
6758                 TextLangMan::setHyphenationForceAlgorithmic( enabled );
6759                 REQUEST_RENDER("propsApply textlang hyphenation_force_algorithmic")
6760             }
6761         } else if (name == PROP_INTERLINE_SPACE) {
6762             int interlineSpace = props->getIntDef(PROP_INTERLINE_SPACE,
6763                                                   cr_interline_spaces[0]);
6764             setDefaultInterlineSpace(interlineSpace);//cr_font_sizes
6765             value = lString32::itoa(m_def_interline_space);
6766 #if CR_INTERNAL_PAGE_ORIENTATION==1
6767         } else if ( name==PROP_ROTATE_ANGLE ) {
6768             cr_rotate_angle_t angle = (cr_rotate_angle_t) (props->getIntDef( PROP_ROTATE_ANGLE, 0 )&3);
6769             SetRotateAngle( angle );
6770             value = lString32::itoa( m_rotateAngle );
6771 #endif
6772         } else if (name == PROP_EMBEDDED_STYLES) {
6773             bool value = props->getBoolDef(PROP_EMBEDDED_STYLES, true);
6774             if (m_doc) // not when noDefaultDocument=true
6775                 getDocument()->setDocFlag(DOC_FLAG_ENABLE_INTERNAL_STYLES, value);
6776             REQUEST_RENDER("propsApply embedded styles")
6777         } else if (name == PROP_EMBEDDED_FONTS) {
6778             bool value = props->getBoolDef(PROP_EMBEDDED_FONTS, true);
6779             if (m_doc) // not when noDefaultDocument=true
6780                 getDocument()->setDocFlag(DOC_FLAG_ENABLE_DOC_FONTS, value);
6781             REQUEST_RENDER("propsApply doc fonts")
6782         } else if (name == PROP_NONLINEAR_PAGEBREAK) {
6783             bool value = props->getBoolDef(PROP_NONLINEAR_PAGEBREAK, false);
6784             if (m_doc) // not when noDefaultDocument=true
6785                 getDocument()->setDocFlag(DOC_FLAG_NONLINEAR_PAGEBREAK, value);
6786             REQUEST_RENDER("propsApply nonlinear")
6787         } else if (name == PROP_FOOTNOTES) {
6788             bool value = props->getBoolDef(PROP_FOOTNOTES, true);
6789             if (m_doc) // not when noDefaultDocument=true
6790                 getDocument()->setDocFlag(DOC_FLAG_ENABLE_FOOTNOTES, value);
6791             REQUEST_RENDER("propsApply footnotes")
6792         } else if (name == PROP_FLOATING_PUNCTUATION) {
6793             bool value = props->getBoolDef(PROP_FLOATING_PUNCTUATION, true);
6794             if (m_doc) // not when noDefaultDocument=true
6795                 if (getDocument()->setHangingPunctiationEnabled(value)) {
6796                     REQUEST_RENDER("propsApply - hanging punctuation")
6797                     // requestRender() does m_doc->clearRendBlockCache(), which is needed
6798                     // on hanging punctuation change
6799                 }
6800         } else if (name == PROP_REQUESTED_DOM_VERSION) {
6801             int value = props->getIntDef(PROP_REQUESTED_DOM_VERSION, gDOMVersionCurrent);
6802             if (m_doc) // not when noDefaultDocument=true
6803                 if (getDocument()->setDOMVersionRequested(value)) {
6804                     needUpdateHyphenation = true;
6805                     REQUEST_RENDER("propsApply requested dom version")
6806                 }
6807         } else if (name == PROP_RENDER_BLOCK_RENDERING_FLAGS) {
6808             lUInt32 value = (lUInt32)props->getIntDef(PROP_RENDER_BLOCK_RENDERING_FLAGS, BLOCK_RENDERING_FLAGS_DEFAULT);
6809             if (m_doc) // not when noDefaultDocument=true
6810                 if (getDocument()->setRenderBlockRenderingFlags(value))
6811                     REQUEST_RENDER("propsApply render block rendering flags")
6812         } else if (name == PROP_RENDER_DPI) {
6813             int value = props->getIntDef(PROP_RENDER_DPI, DEF_RENDER_DPI);
6814             if ( gRenderDPI != value ) {
6815                 gRenderDPI = value;
6816                 REQUEST_RENDER("propsApply render dpi")
6817             }
6818         } else if (name == PROP_RENDER_SCALE_FONT_WITH_DPI) {
6819             int value = props->getIntDef(PROP_RENDER_SCALE_FONT_WITH_DPI, DEF_RENDER_SCALE_FONT_WITH_DPI);
6820             if ( gRenderScaleFontWithDPI != value ) {
6821                 gRenderScaleFontWithDPI = value;
6822                 REQUEST_RENDER("propsApply render scale font with dpi")
6823             }
6824         } else if (name == PROP_FORMAT_SPACE_WIDTH_SCALE_PERCENT) {
6825             int value = props->getIntDef(PROP_FORMAT_SPACE_WIDTH_SCALE_PERCENT, DEF_SPACE_WIDTH_SCALE_PERCENT);
6826             if (m_doc) // not when noDefaultDocument=true
6827                 if (getDocument()->setSpaceWidthScalePercent(value))
6828                     REQUEST_RENDER("propsApply space width scale percent")
6829         } else if (name == PROP_FORMAT_MIN_SPACE_CONDENSING_PERCENT) {
6830             int value = props->getIntDef(PROP_FORMAT_MIN_SPACE_CONDENSING_PERCENT, DEF_MIN_SPACE_CONDENSING_PERCENT);
6831             if (m_doc) // not when noDefaultDocument=true
6832                 if (getDocument()->setMinSpaceCondensingPercent(value))
6833                     REQUEST_RENDER("propsApply condensing percent")
6834         } else if (name == PROP_FORMAT_UNUSED_SPACE_THRESHOLD_PERCENT) {
6835             int value = props->getIntDef(PROP_FORMAT_UNUSED_SPACE_THRESHOLD_PERCENT, DEF_UNUSED_SPACE_THRESHOLD_PERCENT);
6836             if (m_doc) // not when noDefaultDocument=true
6837                 if (getDocument()->setUnusedSpaceThresholdPercent(value))
6838                     REQUEST_RENDER("propsApply unused space threshold percent")
6839         } else if (name == PROP_FORMAT_MAX_ADDED_LETTER_SPACING_PERCENT) {
6840             int value = props->getIntDef(PROP_FORMAT_MAX_ADDED_LETTER_SPACING_PERCENT, DEF_MAX_ADDED_LETTER_SPACING_PERCENT);
6841             if (m_doc) // not when noDefaultDocument=true
6842                 if (getDocument()->setMaxAddedLetterSpacingPercent(value))
6843                     REQUEST_RENDER("propsApply max added letter spacing percent")
6844         } else if (name == PROP_HIGHLIGHT_COMMENT_BOOKMARKS) {
6845             int value = props->getIntDef(PROP_HIGHLIGHT_COMMENT_BOOKMARKS, highlight_mode_underline);
6846             if (m_highlightBookmarks != value) {
6847                 m_highlightBookmarks = value;
6848                 updateBookMarksRanges();
6849             }
6850             REQUEST_RENDER("propsApply - PROP_HIGHLIGHT_COMMENT_BOOKMARKS")
6851         } else if (name == PROP_PAGE_VIEW_MODE) {
6852             LVDocViewMode m =
6853                     props->getIntDef(PROP_PAGE_VIEW_MODE, 1) ? DVM_PAGES
6854                                                              : DVM_SCROLL;
6855             setViewMode(m);
6856         } else if (name == PROP_CACHE_VALIDATION_ENABLED) {
6857             bool value = props->getBoolDef(PROP_CACHE_VALIDATION_ENABLED, true);
6858             enableCacheFileContentsValidation(value);
6859         } else {
6860 
6861             // unknown property, adding to list of unknown properties
6862             unknown->setString(name.c_str(), value);
6863         }
6864         // Update current value in properties
6865         // Even if not used above to set anything if no m_doc, this saves
6866         // the value in m_props so it might be used by createEmptyDocument()
6867         // when creating the coming up document (and further documents
6868         // if the value is not re-set).
6869         m_props->setString(name.c_str(), value);
6870     }
6871     if (needUpdateMargins)
6872         updatePageMargins();
6873     if (needUpdateHyphenation) {
6874         bool legacyRendering = m_props->getIntDef(PROP_RENDER_BLOCK_RENDERING_FLAGS, BLOCK_RENDERING_FLAGS_DEFAULT) == 0 ||
6875                 m_props->getIntDef(PROP_REQUESTED_DOM_VERSION, gDOMVersionCurrent) < 20180524;
6876         if (legacyRendering) {
6877             // hyphenation dictionary
6878             lString32 id = m_props->getStringDef(PROP_HYPHENATION_DICT,
6879                                                DEF_HYPHENATION_DICT);
6880             CRLog::debug("PROP_HYPHENATION_DICT = %s", LCSTR(id));
6881             HyphDictionaryList * list = HyphMan::getDictList();
6882             if (list) {
6883                 CRLog::debug("Changing hyphenation to %s", LCSTR(id));
6884                 list->activate(id);
6885                 REQUEST_RENDER("propsApply hyphenation dict")
6886             }
6887         } else {
6888             bool embeddedLang = m_props->getBoolDef(PROP_TEXTLANG_EMBEDDED_LANGS_ENABLED, 0) != 0;
6889             lString32 mainLang = m_props->getStringDef(PROP_TEXTLANG_MAIN_LANG, TEXTLANG_DEFAULT_MAIN_LANG);
6890             bool hyphEnabled = m_props->getIntDef(PROP_TEXTLANG_HYPHENATION_ENABLED, TEXTLANG_DEFAULT_HYPHENATION_ENABLED) != 0;
6891             if (embeddedLang != TextLangMan::getEmbeddedLangsEnabled()) {
6892                 TextLangMan::setEmbeddedLangsEnabled(embeddedLang);
6893                 REQUEST_RENDER("propsApply textlang embedded_langs_enabled")
6894             }
6895             if (mainLang != TextLangMan::getMainLang()) {
6896                 TextLangMan::setMainLang(mainLang);
6897                 REQUEST_RENDER("propsApply textlang main_lang")
6898             }
6899             if (hyphEnabled != TextLangMan::getHyphenationEnabled()) {
6900                 TextLangMan::setHyphenationEnabled(hyphEnabled);
6901                 REQUEST_RENDER("propsApply textlang hyphenation_enabled")
6902             }
6903             if (!TextLangMan::getEmbeddedLangsEnabled()) {
6904                 // hyphenation dictionary
6905                 lString32 id = m_props->getStringDef(PROP_HYPHENATION_DICT,
6906                                                    DEF_HYPHENATION_DICT);
6907                 CRLog::debug("PROP_HYPHENATION_DICT = %s", LCSTR(id));
6908                 HyphDictionaryList * list = HyphMan::getDictList();
6909                 if (list) {
6910                     CRLog::debug("Changing hyphenation to %s", LCSTR(id));
6911                     list->activate(id);
6912                     REQUEST_RENDER("propsApply hyphenation dict")
6913                 }
6914             }
6915         }
6916     }
6917     return unknown;
6918 }
6919 
6920 /// returns current values of supported properties
propsGetCurrent()6921 CRPropRef LVDocView::propsGetCurrent() {
6922 	return m_props;
6923 }
6924 
updateSelection()6925 void LVPageWordSelector::updateSelection()
6926 {
6927     LVArray<ldomWord> list;
6928     if ( _words.getSelWord() )
6929         list.add(_words.getSelWord()->getWord() );
6930     if ( list.length() )
6931         _docview->selectWords(list);
6932     else
6933         _docview->clearSelection();
6934 }
6935 
~LVPageWordSelector()6936 LVPageWordSelector::~LVPageWordSelector()
6937 {
6938     _docview->clearSelection();
6939 }
6940 
LVPageWordSelector(LVDocView * docview)6941 LVPageWordSelector::LVPageWordSelector( LVDocView * docview )
6942     : _docview(docview)
6943 {
6944     LVRef<ldomXRange> range = _docview->getPageDocumentRange();
6945     if (!range.isNull()) {
6946 		_words.addRangeWords(*range, true);
6947                 if (_docview->getVisiblePageCount() > 1) { // _docview->isPageMode() &&
6948                         // process second page
6949                         int pageNumber = _docview->getCurPage();
6950                         range = _docview->getPageDocumentRange(pageNumber + 1);
6951                         if (!range.isNull())
6952                             _words.addRangeWords(*range, true);
6953                 }
6954 		_words.selectMiddleWord();
6955 		updateSelection();
6956 	}
6957 }
6958 
moveBy(MoveDirection dir,int distance)6959 void LVPageWordSelector::moveBy( MoveDirection dir, int distance )
6960 {
6961     _words.selectNextWord(dir, distance);
6962     updateSelection();
6963 }
6964 
selectWord(int x,int y)6965 void LVPageWordSelector::selectWord(int x, int y)
6966 {
6967 	ldomWordEx * word = _words.findNearestWord(x, y, DIR_ANY);
6968 	_words.selectWord(word, DIR_ANY);
6969 	updateSelection();
6970 }
6971 
6972 // append chars to search pattern
appendPattern(lString32 chars)6973 ldomWordEx * LVPageWordSelector::appendPattern( lString32 chars )
6974 {
6975     ldomWordEx * res = _words.appendPattern(chars);
6976     if ( res )
6977         updateSelection();
6978     return res;
6979 }
6980 
6981 // remove last item from pattern
reducePattern()6982 ldomWordEx * LVPageWordSelector::reducePattern()
6983 {
6984     ldomWordEx * res = _words.reducePattern();
6985     if ( res )
6986         updateSelection();
6987     return res;
6988 }
6989 
SimpleTitleFormatter(lString32 text,lString8 fontFace,bool bold,bool italic,lUInt32 color,int maxWidth,int maxHeight,int fntSize)6990 SimpleTitleFormatter::SimpleTitleFormatter(lString32 text, lString8 fontFace, bool bold, bool italic, lUInt32 color, int maxWidth, int maxHeight, int fntSize) : _text(text), _fontFace(fontFace), _bold(bold), _italic(italic), _color(color), _maxWidth(maxWidth), _maxHeight(maxHeight), _fntSize(fntSize) {
6991     if (_text.length() > 80)
6992         _text = _text.substr(0, 80) + "...";
6993     if (findBestSize())
6994         return;
6995     _text = _text.substr(0, 50) + "...";
6996     if (findBestSize())
6997         return;
6998     _text = _text.substr(0, 32) + "...";
6999     if (findBestSize())
7000         return;
7001     _text = _text.substr(0, 16) + "...";
7002     if (findBestSize())
7003         return;
7004     // Fallback to tiny font
7005     format(2);
7006 }
7007 
measure()7008 bool SimpleTitleFormatter::measure() {
7009     _width = 0;
7010     _height = 0;
7011     for (int i=_lines.length() - 1; i >= 0; i--) {
7012         lString32 line = _lines[i].trim();
7013         int w = _font->getTextWidth(line.c_str(), line.length());
7014         if (w > _width)
7015             _width = w;
7016         _height += _lineHeight;
7017     }
7018     return _width < _maxWidth && _height < _maxHeight;
7019 }
splitLines(const char * delimiter)7020 bool SimpleTitleFormatter::splitLines(const char * delimiter) {
7021     lString32 delim32(delimiter);
7022     int bestpos = -1;
7023     int bestdist = -1;
7024     int start = 0;
7025     bool skipDelimiter = *delimiter == '|';
7026     for (;;) {
7027         int p = _text.pos(delim32, start);
7028         if (p < 0)
7029             break;
7030         int dist = _text.length() / 2 - p;
7031         if (dist < 0)
7032             dist = -dist;
7033         if (bestdist == -1 || dist < bestdist) {
7034             bestdist = dist;
7035             bestpos = p;
7036         }
7037         start = p + 1;
7038     }
7039     if (bestpos < 0)
7040         return false;
7041     _lines.add(_text.substr(0, bestpos + (skipDelimiter ? 0 : delim32.length())).trim());
7042     _lines.add(_text.substr(bestpos + delim32.length()).trim());
7043     return measure();
7044 }
format(int fontSize)7045 bool SimpleTitleFormatter::format(int fontSize) {
7046     _font = fontMan->GetFont(fontSize, _bold ? 800 : 400, _italic, css_ff_sans_serif, _fontFace, 0, -1);
7047     _lineHeight = _font->getHeight() * 120 / 100;
7048     _lines.clear();
7049     _height = 0;
7050     int singleLineWidth = _font->getTextWidth(_text.c_str(), _text.length());
7051     if (singleLineWidth < _maxWidth) {
7052         _lines.add(_text);
7053         _width = singleLineWidth;
7054         _height = _lineHeight;
7055         return _width < _maxWidth && _height < _maxHeight;
7056     }
7057     if (splitLines("|"))
7058         return true;
7059     if (splitLines(","))
7060         return true;
7061     if (splitLines(";"))
7062         return true;
7063     if (splitLines(":"))
7064         return true;
7065     if (splitLines("-"))
7066         return true;
7067     if (splitLines(" "))
7068         return true;
7069     if (splitLines("_"))
7070         return true;
7071     if (splitLines("."))
7072         return true;
7073     _lines.clear();
7074     int p = _text.length() / 2;
7075     _lines.add(_text.substr(0, p));
7076     _lines.add(_text.substr(p, _text.length() - p));
7077     return false;
7078 }
findBestSize()7079 bool SimpleTitleFormatter::findBestSize() {
7080     if (_fntSize) {
7081         format(_fntSize);
7082         return true;
7083     }
7084     int maxSizeW = _maxWidth / 10;
7085     int maxSizeH = _maxHeight / 3;
7086     int maxSize = maxSizeW < maxSizeH ? maxSizeW : maxSizeH;
7087     if (maxSize > 50)
7088         maxSize = 50;
7089     int minSize = 11;
7090     for (int size = maxSize; size >= minSize; ) {
7091         if (format(size))
7092             return true;
7093         if (size > 30)
7094             size -= 3;
7095         else if (size > 20)
7096             size -= 2;
7097         else
7098             size--;
7099     }
7100     return false;
7101 }
draw(LVDrawBuf & buf,lString32 str,int x,int y,int align)7102 void SimpleTitleFormatter::draw(LVDrawBuf & buf, lString32 str, int x, int y, int align) {
7103     int w = _font->getTextWidth(str.c_str(), str.length());
7104     if (align == 0)
7105         x -= w / 2; // center
7106     else if (align == 1)
7107         x -= w; // right
7108     buf.SetTextColor(_color);
7109     _font->DrawTextString(&buf, x, y, str.c_str(), str.length(), '?');
7110 }
draw(LVDrawBuf & buf,lvRect rc,int halign,int valign)7111 void SimpleTitleFormatter::draw(LVDrawBuf & buf, lvRect rc, int halign, int valign) {
7112     int y0 = rc.top;
7113     if (valign == 0)
7114         y0 += (rc.height() - _lines.length() * _lineHeight) / 2;
7115     int x0 = halign < 0 ? rc.left : (halign > 0 ? rc.right : (rc.left + rc.right) / 2);
7116     for (int i=0; i<_lines.length(); i++) {
7117         draw(buf, _lines[i], x0, y0, halign);
7118         y0 += _lineHeight;
7119     }
7120 }
7121 
7122 struct cover_palette_t {
7123     lUInt32 frame;
7124     lUInt32 bg;
7125     lUInt32 hline;
7126     lUInt32 vline;
7127     lUInt32 title;
7128     lUInt32 authors;
7129     lUInt32 series;
7130     lUInt32 titleframe;
7131 };
7132 
7133 static cover_palette_t gray_palette[1] = {
7134     // frame,    bg,         hline,      vline,      title,      authors,    series      titleframe
7135     {0x00C0C0C0, 0x00FFFFFF, 0xC0FFC040, 0xC0F0D060, 0x00000000, 0x00000000, 0x00000000, 0x40FFFFFF},
7136 };
7137 
7138 static cover_palette_t normal_palette[8] = {
7139     // frame,    bg,         hline,      vline,      title,      authors,    series
7140     {0x00D0D0D0, 0x00E8E0E0, 0xC0CFC040, 0xC0F0E060, 0x00600000, 0x00000040, 0x00404040, 0x80FFFFFF},
7141     {0x00D0D0D0, 0x00E0E8E0, 0xC0FFC040, 0xD0F0D080, 0x00800000, 0x00000080, 0x00406040, 0x80FFFFFF},
7142     {0x00D0D0D0, 0x00E0E0E8, 0xC0CFC040, 0xC0F0D060, 0x00A00000, 0x00000060, 0x00404060, 0x80FFEFFF},
7143     {0x00D0D0D0, 0x00E0E8E8, 0xC0FFC040, 0xD0F0E060, 0x00800000, 0x00000080, 0x00406040, 0x80FFFFFF},
7144     {0x00D0D0D0, 0x00E8E8E0, 0xC0EFC040, 0xC0F0D060, 0x00400000, 0x00000050, 0x00404040, 0x80FFEFFF},
7145     {0x00D0D0D0, 0x00E8E0E8, 0xC0FFC040, 0xC0F0E040, 0x00000000, 0x00000080, 0x00606040, 0x80FFFFFF},
7146     {0x00D0D0D0, 0x00E8E8E8, 0xC0EFC040, 0xD0F0D060, 0x00600000, 0x000000A0, 0x00402040, 0x80FFEFFF},
7147     {0x00D0D0D0, 0x00E0F0E0, 0xC0FFC040, 0xC0F0D080, 0x00400000, 0x00000080, 0x00402020, 0x80FFFFFF},
7148 };
7149 
7150 static cover_palette_t series_palette[8] = {
7151     // frame,    bg,         hline,      vline,      title,      authors,    series
7152     {0x00C0D0C0, 0x20DFD0E0, 0xD0FFC040, 0xD0F0D080, 0x00800000, 0x00000080, 0x00406040, 0x80FFFFFF},
7153     {0x00C0C0D0, 0x00D0FFD0, 0xD0EFC040, 0xD0D0F060, 0x00400000, 0x00400040, 0x00404040, 0xA0FFFFFF},
7154     {0x00B0C0C0, 0x20D0D0FF, 0xE0FFC040, 0xD0F0D0E0, 0x00600000, 0x00000080, 0x00406040, 0x60FFEFFF},
7155     {0x00C0D0C0, 0x20D0FFFF, 0xC0EFC040, 0xD0FFD060, 0x00804000, 0x00000060, 0x00402020, 0x80FFFFFF},
7156     {0x00C0C0B0, 0x20FFFFD0, 0xC0EFC040, 0xD0FFD060, 0x00400040, 0x00000060, 0x00406040, 0xB0FFEFFF},
7157     {0x00D0C0C0, 0x20DFE0FF, 0xE0FFC040, 0xD0FFD0E0, 0x00804040, 0x00000080, 0x00204040, 0xA0FFFFFF},
7158     {0x00D0C0D0, 0x20E0E0F0, 0xC0FFC040, 0xD0FFE0E0, 0x00400000, 0x00400040, 0x00204040, 0xB0FFEFFF},
7159     {0x00C0D0C0, 0x20E8E8D8, 0xC0FFC040, 0xD0F0E0E0, 0x00400040, 0x00000080, 0x00402040, 0x80FFFFFF},
7160 };
7161 
LVDrawBookCover(LVDrawBuf & buf,LVImageSourceRef image,lString8 fontFace,lString32 title,lString32 authors,lString32 seriesName,int seriesNumber)7162 void LVDrawBookCover(LVDrawBuf & buf, LVImageSourceRef image, lString8 fontFace, lString32 title, lString32 authors, lString32 seriesName, int seriesNumber) {
7163     CR_UNUSED(seriesNumber);
7164     bool isGray = buf.GetBitsPerPixel() <= 8;
7165     cover_palette_t * palette = NULL;
7166     if (isGray)
7167         palette = &gray_palette[0];
7168     else if (!seriesName.empty())
7169         palette = &series_palette[getHash(seriesName) & 7];
7170     else if (!authors.empty())
7171         palette = &normal_palette[getHash(authors) & 7];
7172     else
7173         palette = &normal_palette[getHash(title) & 7];
7174     int dx = buf.GetWidth();
7175     int dy = buf.GetHeight();
7176     if (!image.isNull() && image->GetWidth() > 0 && image->GetHeight() > 0) {
7177         CRLog::trace("drawing image cover page %d x %d", dx, dy);
7178         buf.Draw(image, 0, 0, dx, dy);
7179 		return;
7180 	}
7181 
7182 
7183     CRLog::trace("drawing default cover page %d x %d", dx, dy);
7184 	lvRect rc(0, 0, buf.GetWidth(), buf.GetHeight());
7185     buf.FillRect(rc, palette->frame);
7186 	rc.shrink(rc.width() / 40);
7187     buf.FillRect(rc, palette->bg);
7188 
7189 	lvRect rc2(rc);
7190 	rc2.top = rc.height() * 8 / 10;
7191 	rc2.bottom = rc2.top + rc.height() / 15;
7192     buf.FillRect(rc2, palette->hline);
7193 
7194 	lvRect rc3(rc);
7195 	rc3.left += rc.width() / 30;
7196 	rc3.right = rc3.left + rc.width() / 30;
7197     buf.FillRect(rc3, palette->vline);
7198 
7199 
7200 	LVFontRef fnt = fontMan->GetFont(16, 400, false, css_ff_sans_serif, fontFace, 0, -1); // = fontMan
7201 	if (!fnt.isNull()) {
7202 
7203 		rc.left += rc.width() / 10;
7204 		rc.right -= rc.width() / 20;
7205 
7206         lUInt32 titleColor = palette->title;
7207         lUInt32 authorColor = palette->authors;
7208         lUInt32 seriesColor = palette->series;
7209 
7210 		lvRect authorRc(rc);
7211 
7212 		if (!authors.empty()) {
7213 			authorRc.top += rc.height() * 1 / 20;
7214 			authorRc.bottom = authorRc.top + rc.height() * 2 / 10;
7215 			SimpleTitleFormatter authorFmt(authors, fontFace, false, false, authorColor, authorRc.width(), authorRc.height());
7216 			authorFmt.draw(buf, authorRc, 0, 0);
7217 		} else {
7218 			authorRc.bottom = authorRc.top;
7219 		}
7220 
7221 		if (!title.empty()) {
7222 			lvRect titleRc(rc);
7223 			titleRc.top += rc.height() * 4 / 10;
7224 			titleRc.bottom = titleRc.top + rc.height() * 7 / 10;
7225 
7226 			lvRect rc3(titleRc);
7227 			rc3.top -= rc.height() / 20;
7228 			rc3.bottom = rc3.top + rc.height() / 40;
7229             buf.FillRect(rc3, palette->titleframe);
7230 
7231 			SimpleTitleFormatter titleFmt(title, fontFace, true, false, titleColor, titleRc.width(), titleRc.height());
7232 			titleFmt.draw(buf, titleRc, -1, -1);
7233 
7234 			rc3.top += titleFmt.getHeight() + rc.height() / 20;
7235 			rc3.bottom = rc3.top + rc.height() / 40;
7236             buf.FillRect(rc3, palette->titleframe);
7237 		}
7238 
7239 		if (!seriesName.empty()) {
7240 			lvRect seriesRc(rc);
7241 			seriesRc.top += rc.height() * 8 / 10;
7242 			//seriesRc.bottom = rc.top + rc.height() * 9 / 10;
7243 			SimpleTitleFormatter seriesFmt(seriesName, fontFace, false, true, seriesColor, seriesRc.width(), seriesRc.height());
7244 			seriesFmt.draw(buf, seriesRc, 1, 0);
7245 		}
7246 
7247     } else {
7248         CRLog::error("Cannot get font for coverpage");
7249     }
7250 }
7251 
7252