bool CbxEngineImpl::LoadCbrFile(const WCHAR *file) { if (!file) return false; fileName = str::Dup(file); fileExt = L".cbr"; RAROpenArchiveDataEx arcData = { 0 }; arcData.ArcNameW = (WCHAR *)file; arcData.OpenMode = RAR_OM_EXTRACT; HANDLE hArc = RAROpenArchiveEx(&arcData); if (!hArc || arcData.OpenResult != 0) return false; // UnRAR does not seem to support extracting a single file by name, // so lazy image loading doesn't seem possible Vec<ImagesPage *> found; for (;;) { RARHeaderDataEx rarHeader; int res = RARReadHeaderEx(hArc, &rarHeader); if (0 != res) break; const WCHAR *fileName = rarHeader.FileNameW; if (ImageEngine::IsSupportedFile(fileName)) { ImagesPage *page = LoadCurrentCbrPage(hArc, rarHeader); if (page) found.Append(page); } else if (str::EqI(fileName, L"ComicInfo.xml")) { ScopedMem<char> xmlData(LoadCurrentCbrFile(hArc, rarHeader, NULL)); if (xmlData) ParseComicInfoXml(xmlData); } else RARProcessFile(hArc, RAR_SKIP, NULL, NULL); } RARCloseArchive(hArc); if (found.Count() == 0) return false; found.Sort(ImagesPage::cmpPageByName); for (size_t i = 0; i < found.Count(); i++) { pages.Append(found.At(i)->bmp); found.At(i)->bmp = NULL; } mediaboxes.AppendBlanks(pages.Count()); DeleteVecMembers(found); return true; }
void PaintSelection(WindowInfo* win, HDC hdc) { CrashIf(!win->AsFixed()); Vec<RectI> rects; if (win->mouseAction == MouseAction::Selecting) { // during rectangle selection RectI selRect = win->selectionRect; if (selRect.dx < 0) { selRect.x += selRect.dx; selRect.dx *= -1; } if (selRect.dy < 0) { selRect.y += selRect.dy; selRect.dy *= -1; } rects.Append(selRect); } else { // during text selection or after selection is done if (MouseAction::SelectingText == win->mouseAction) { UpdateTextSelection(win); if (!win->currentTab->selectionOnPage) { // prevent the selection from disappearing while the // user is still at it (OnSelectionStop removes it // if it is still empty at the end) win->currentTab->selectionOnPage = new Vec<SelectionOnPage>(); win->showSelection = true; } } CrashIf(!win->currentTab->selectionOnPage); if (!win->currentTab->selectionOnPage) return; for (SelectionOnPage& sel : *win->currentTab->selectionOnPage) { rects.Append(sel.GetRect(win->AsFixed())); } } PaintTransparentRectangles(hdc, win->canvasRc, rects, gGlobalPrefs->fixedPageUI.selectionColor); }
void DictTestMapStrToInt() { dict::MapStrToInt d(4); // start small so that we can test resizing bool ok; int val; utassert(0 == d.Count()); ok = d.Get("foo", &val); utassert(!ok); ok = d.Remove("foo", nullptr); utassert(!ok); ok = d.Insert("foo", 5, nullptr); utassert(ok); utassert(1 == d.Count()); ok = d.Get("foo", &val); utassert(ok); utassert(val == 5); ok = d.Insert("foo", 8, &val); utassert(!ok); utassert(val == 5); ok = d.Get("foo", &val); utassert(ok); utassert(val == 5); ok = d.Get("bar", &val); utassert(!ok); val = 0; ok = d.Remove("foo", &val); utassert(ok); utassert(val == 5); utassert(0 == d.Count()); srand((unsigned int)time(nullptr)); Vec<char *> toRemove; for (int i=0; i < 1024; i++) { char *k = GenRandomString(); ok = d.Insert(k, i, nullptr); // no guarantee that the string is unique, so Insert() doesn't always succeeds if (!ok) continue; toRemove.Append(str::Dup(k)); utassert(toRemove.Count() == d.Count()); ok = d.Get(k, &val); CrashIf(!ok); CrashIf(i != val); } for (const char *k : toRemove) { ok = d.Remove(k, nullptr); utassert(ok); } FreeVecMembers(toRemove); }
// Select random files to test. We want to test each file type equally, so // we first group them by file extension and then select up to maxPerType // for each extension, randomly, and inter-leave the files with different // extensions, so their testing is evenly distributed. // Returns result in <files>. static void RandomizeFiles(WStrVec& files, int maxPerType) { WStrVec fileExts; Vec<WStrVec *> filesPerType; for (size_t i = 0; i < files.Count(); i++) { const WCHAR *file = files.At(i); const WCHAR *ext = path::GetExt(file); CrashAlwaysIf(!ext); int typeNo = fileExts.FindI(ext); if (-1 == typeNo) { fileExts.Append(str::Dup(ext)); filesPerType.Append(new WStrVec()); typeNo = (int)filesPerType.Count() - 1; } filesPerType.At(typeNo)->Append(str::Dup(file)); } for (size_t j = 0; j < filesPerType.Count(); j++) { WStrVec *all = filesPerType.At(j); WStrVec *random = new WStrVec(); for (int n = 0; n < maxPerType && all->Count() > 0; n++) { int idx = rand() % all->Count(); WCHAR *file = all->At(idx); random->Append(file); all->RemoveAtFast(idx); } filesPerType.At(j) = random; delete all; } files.Reset(); bool gotAll = false; while (!gotAll) { gotAll = true; for (size_t j = 0; j < filesPerType.Count(); j++) { WStrVec *random = filesPerType.At(j); if (random->Count() > 0) { gotAll = false; WCHAR *file = random->At(0); files.Append(file); random->RemoveAtFast(0); } } } for (size_t j = 0; j < filesPerType.Count(); j++) { delete filesPerType.At(j); } }
static Vec<PageAnnotation> *ParseFileModifications(const char *data) { if (!data) return NULL; SquareTree sqt(data); if (!sqt.root || sqt.root->data.Count() == 0) return NULL; SquareTreeNode::DataItem& item = sqt.root->data.At(0); if (!item.isChild || !str::EqI(item.key, "@meta")) return NULL; if (!item.value.child->GetValue("version")) { // don't check the version value - rather extend the format // in a way to ensure backwards compatibility return NULL; } Vec<PageAnnotation> *list = new Vec<PageAnnotation>(); for (SquareTreeNode::DataItem *i = sqt.root->data.IterStart(); i; i = sqt.root->data.IterNext()) { PageAnnotType type = str::EqI(i->key, "highlight") ? Annot_Highlight : str::EqI(i->key, "underline") ? Annot_Underline : str::EqI(i->key, "strikeout") ? Annot_StrikeOut : str::EqI(i->key, "squiggly") ? Annot_Squiggly : Annot_None; CrashIf(!i->isChild); if (Annot_None == type || !i->isChild) continue; int pageNo; geomutil::RectT<float> rect; PageAnnotation::Color color; float opacity; int r, g, b; SquareTreeNode *node = i->value.child; const char *value = node->GetValue("page"); if (!value || !str::Parse(value, "%d%$", &pageNo)) continue; value = node->GetValue("rect"); if (!value || !str::Parse(value, "%f %f %f %f%$", &rect.x, &rect.y, &rect.dx, &rect.dy)) continue; value = node->GetValue("color"); if (!value || !str::Parse(value, "#%2x%2x%2x%$", &r, &g, &b)) continue; value = node->GetValue("opacity"); if (!value || !str::Parse(value, "%f%$", &opacity)) opacity = 1.0f; color = PageAnnotation::Color((uint8_t)r, (uint8_t)g, (uint8_t)b, (uint8_t)(255 * opacity)); list->Append(PageAnnotation(type, pageNo, rect.Convert<double>(), color)); } return list; }
void PaintSelection(WindowInfo *win, HDC hdc) { Vec<RectI> rects; if (win->mouseAction == MA_SELECTING) { // during rectangle selection RectI selRect = win->selectionRect; if (selRect.dx < 0) { selRect.x += selRect.dx; selRect.dx *= -1; } if (selRect.dy < 0) { selRect.y += selRect.dy; selRect.dy *= -1; } rects.Append(selRect); } else { // during text selection or after selection is done if (MA_SELECTING_TEXT == win->mouseAction) { UpdateTextSelection(win); if (!win->selectionOnPage) { // prevent the selection from disappearing while the // user is still at it (OnSelectionStop removes it // if it is still empty at the end) win->selectionOnPage = new Vec<SelectionOnPage>(); win->showSelection = true; } } CrashIf(!win->selectionOnPage); if (!win->selectionOnPage) return; for (size_t i = 0; i < win->selectionOnPage->Count(); i++) rects.Append(win->selectionOnPage->At(i).GetRect(win->dm)); } PaintTransparentRectangles(hdc, win->canvasRc, rects, COL_SELECTION_RECT); }
// parses a list of page ranges such as 1,3-5,7- (i..e all but pages 2 and 6) // into an interable list (returns nullptr on parsing errors) // caller must delete the result bool ParsePageRanges(const WCHAR* ranges, Vec<PageRange>& result) { if (!ranges) return false; WStrVec rangeList; rangeList.Split(ranges, L",", true); rangeList.SortNatural(); for (size_t i = 0; i < rangeList.size(); i++) { int start, end; if (str::Parse(rangeList.at(i), L"%d-%d%$", &start, &end) && 0 < start && start <= end) result.Append(PageRange(start, end)); else if (str::Parse(rangeList.at(i), L"%d-%$", &start) && 0 < start) result.Append(PageRange(start, INT_MAX)); else if (str::Parse(rangeList.at(i), L"%d%$", &start) && 0 < start) result.Append(PageRange(start, start)); else return false; } return result.size() > 0; }
void FileHistory::Clear(bool keepFavorites) { if (!states) return; Vec<DisplayState*> keep; for (size_t i = 0; i < states->size(); i++) { if (keepFavorites && states->at(i)->favorites->size() > 0) { states->at(i)->openCount = 0; keep.Append(states->at(i)); } else { DeleteDisplayState(states->at(i)); } } *states = keep; }
RectF MeasureTextQuick(Graphics *g, Font *f, const WCHAR *s, int len) { CrashIf(0 >= len); static Vec<Font *> fontCache; static Vec<bool> fixCache; RectF bbox; g->MeasureString(s, len, f, PointF(0, 0), &bbox); int idx = fontCache.Find(f); if (-1 == idx) { LOGFONTW lfw; Status ok = f->GetLogFontW(g, &lfw); bool isItalicOrMonospace = Ok != ok || lfw.lfItalic || str::Eq(lfw.lfFaceName, L"Courier New") || str::Find(lfw.lfFaceName, L"Consol") || str::EndsWith(lfw.lfFaceName, L"Mono") || str::EndsWith(lfw.lfFaceName, L"Typewriter"); fontCache.Append(f); fixCache.Append(isItalicOrMonospace); idx = (int)fontCache.Count() - 1; } // most documents look good enough with these adjustments if (!fixCache.At(idx)) { REAL correct = 0; for (int i = 0; i < len; i++) { switch (s[i]) { case 'i': case 'l': correct += 0.2f; break; case 't': case 'f': case 'I': correct += 0.1f; break; case '.': case ',': case '!': correct += 0.1f; break; } } bbox.Width *= (1.0f - correct / len) * 0.99f; } bbox.Height *= 0.95f; return bbox; }
void StressTest::Start(const TCHAR *path, const TCHAR *filter, const TCHAR *ranges, int cycles) { srand((unsigned int)time(NULL)); GetSystemTime(&stressStartTime); // forbid entering sleep mode during tests SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED | ES_DISPLAY_REQUIRED); basePath.Set(str::Dup(path)); fileFilter.Set(filter && !str::Eq(filter, _T("*")) ? str::Dup(filter) : NULL); if (file::Exists(basePath)) { filesToOpen.Append(str::Dup(basePath)); ParsePageRanges(ranges, pageRanges); } else if (dir::Exists(basePath)) { OpenDir(basePath); ParsePageRanges(ranges, fileRanges); } else { // Note: dev only, don't translate ScopedMem<TCHAR> s(str::Format(_T("Path '%s' doesn't exist"), path)); ShowNotification(win, s, false /* autoDismiss */, true, NG_STRESS_TEST_SUMMARY); Finished(false); return; } this->cycles = cycles; if (pageRanges.Count() == 0) pageRanges.Append(PageRange()); if (fileRanges.Count() == 0) fileRanges.Append(PageRange()); if (GoToNextFile()) TickTimer(); else Finished(true); }
static void ApplyPrintSettings(const WCHAR *settings, int pageCount, Vec<PRINTPAGERANGE>& ranges, Print_Advanced_Data& advanced) { WStrVec rangeList; if (settings) rangeList.Split(settings, L",", true); for (size_t i = 0; i < rangeList.Count(); i++) { PRINTPAGERANGE pr; if (str::Parse(rangeList.At(i), L"%d-%d%$", &pr.nFromPage, &pr.nToPage)) { pr.nFromPage = limitValue(pr.nFromPage, (DWORD)1, (DWORD)pageCount); pr.nToPage = limitValue(pr.nToPage, (DWORD)1, (DWORD)pageCount); ranges.Append(pr); } else if (str::Parse(rangeList.At(i), L"%d%$", &pr.nFromPage)) { pr.nFromPage = pr.nToPage = limitValue(pr.nFromPage, (DWORD)1, (DWORD)pageCount); ranges.Append(pr); } else if (str::Eq(rangeList.At(i), L"even")) advanced.range = PrintRangeEven; else if (str::Eq(rangeList.At(i), L"odd")) advanced.range = PrintRangeOdd; else if (str::Eq(rangeList.At(i), L"noscale")) advanced.scale = PrintScaleNone; else if (str::Eq(rangeList.At(i), L"shrink")) advanced.scale = PrintScaleShrink; else if (str::Eq(rangeList.At(i), L"fit")) advanced.scale = PrintScaleFit; else if (str::Eq(rangeList.At(i), L"compat")) advanced.asImage = true; } if (ranges.Count() == 0) { PRINTPAGERANGE pr = { 1, pageCount }; ranges.Append(pr); } }
Vec<SelectionOnPage>* SelectionOnPage::FromTextSelect(TextSel* textSel) { Vec<SelectionOnPage>* sel = new Vec<SelectionOnPage>(textSel->len); for (int i = textSel->len - 1; i >= 0; i--) { RectD rect = textSel->rects[i].Convert<double>(); sel->Append(SelectionOnPage(textSel->pages[i], &rect)); } sel->Reverse(); if (sel->size() == 0) { delete sel; return nullptr; } return sel; }
bool EbookEngine::ExtractPageAnchors() { ScopedCritSec scope(&pagesAccess); DrawInstr *baseAnchor = NULL; for (int pageNo = 1; pageNo <= PageCount(); pageNo++) { Vec<DrawInstr> *pageInstrs = GetHtmlPage(pageNo); if (!pageInstrs) return false; for (size_t k = 0; k < pageInstrs->Count(); k++) { DrawInstr *i = &pageInstrs->At(k); if (InstrAnchor != i->type) continue; anchors.Append(PageAnchor(i, pageNo)); if (k < 2 && str::StartsWith(i->str.s + i->str.len, "\" page_marker />")) baseAnchor = i; } baseAnchors.Append(baseAnchor); } CrashIf(baseAnchors.Count() != pages->Count()); return true; }
Font *GetFont(const WCHAR *name, float size, FontStyle style) { int idx = cache.Find(Entry((WCHAR *)name, size, style)); if (idx != -1) return cache.At(idx).font; Font *font = ::new Font(name, size, style); if (!font) { // fall back to the default font, if a desired font can't be created font = ::new Font(L"Times New Roman", size, style); if (!font) { return cache.Count() > 0 ? cache.At(0).font : NULL; } } cache.Append(Entry(str::Dup(name), size, style, font)); return font; }
ImageData *GetImageData(const char *id, const char *pagePath) { ScopedMem<char> url(NormalizeURL(id, pagePath)); str::UrlDecodeInPlace(url); for (size_t i = 0; i < images.Count(); i++) { if (str::Eq(images.At(i).id, url)) return &images.At(i).base; } ImageData2 data = { 0 }; data.base.data = (char *)doc->GetData(url, &data.base.len); if (!data.base.data) return NULL; data.id = url.StealData(); images.Append(data); return &images.Last().base; }
static WCHAR *ExtractHtmlText(EpubDoc *doc) { size_t len; const char *data = doc->GetTextData(&len); str::Str<char> text(len / 2); HtmlPullParser p(data, len); HtmlToken *t; Vec<HtmlTag> tagNesting; while ((t = p.Next()) != NULL && !t->IsError()) { if (t->IsText() && !tagNesting.Contains(Tag_Head) && !tagNesting.Contains(Tag_Script) && !tagNesting.Contains(Tag_Style)) { // trim whitespace (TODO: also normalize within text?) while (t->sLen > 0 && str::IsWs(t->s[0])) { t->s++; t->sLen--; } while (t->sLen > 0 && str::IsWs(t->s[t->sLen-1])) t->sLen--; if (t->sLen > 0) { text.AppendAndFree(ResolveHtmlEntities(t->s, t->sLen)); text.Append(' '); } } else if (t->IsStartTag()) { // TODO: force-close tags similar to HtmlFormatter.cpp's AutoCloseOnOpen? if (!IsTagSelfClosing(t->tag)) tagNesting.Append(t->tag); } else if (t->IsEndTag()) { if (!IsInlineTag(t->tag) && text.Size() > 0 && text.Last() == ' ') { text.Pop(); text.Append("\r\n"); } // when closing a tag, if the top tag doesn't match but // there are only potentially self-closing tags on the // stack between the matching tag, we pop all of them if (tagNesting.Contains(t->tag)) { while (tagNesting.Last() != t->tag) tagNesting.Pop(); } if (tagNesting.Count() > 0 && tagNesting.Last() == t->tag) tagNesting.Pop(); } } return str::conv::FromUtf8(text.Get()); }
static void ShowProperties(HWND parent, Controller *ctrl, bool extended=false) { PropertiesLayout *layoutData = FindPropertyWindowByParent(parent); if (layoutData) { SetActiveWindow(layoutData->hwnd); return; } if (!ctrl) return; layoutData = new PropertiesLayout(); gPropertiesWindows.Append(layoutData); GetProps(ctrl, layoutData, extended); if (!CreatePropertiesWindow(parent, layoutData)) delete layoutData; }
static void ShowProperties(HWND parent, Doc doc, DisplayModel *dm, bool extended=false) { PropertiesLayout *layoutData = FindPropertyWindowByParent(parent); if (layoutData) { SetActiveWindow(layoutData->hwnd); return; } if (!doc.IsEngine() && !doc.IsEbook()) return; layoutData = new PropertiesLayout(); gPropertiesWindows.Append(layoutData); GetProps(doc, layoutData, dm, extended); if (!CreatePropertiesWindow(parent, layoutData)) delete layoutData; }
static bool ParseSimple(ParserState *state) { Vec<NestingInfo> stack; int parentNodeIdx = -1; char *s = state->curr; for (;;) { if (!*s) break; ParsedLine p; s = ParseSimpleLine(s, p); if (!s) return false; if (p.isComment) continue; while (stack.Count() > 0) { size_t pos = stack.Count() - 1; NestingInfo it = stack.At(pos); if (it.indent >= p.indent) { stack.RemoveAt(pos); } else { parentNodeIdx = it.nodeIdx; break; } } if (parentNodeIdx != -1 && 0 == stack.Count()) return false; int nodeIdx; MarkupNode *node = state->AllocNode(nodeIdx, parentNodeIdx); node->name = p.name; node->attributes = p.attributes; node->user = NULL; state->cb->NewNode(node); NestingInfo it; it.indent = p.indent; it.nodeIdx = nodeIdx; stack.Append(it); } return 0 == stack.Count(); }
// Load and cache data for a given url inside CHM file. bool ChmEngineImpl::GetDataForUrl(const WCHAR *url, char **data, size_t *len) { ScopedMem<WCHAR> plainUrl(str::ToPlainUrl(url)); ChmCacheEntry *e = FindDataForUrl(plainUrl); if (!e) { e = new ChmCacheEntry(plainUrl); ScopedMem<char> urlUtf8(str::conv::ToUtf8(plainUrl)); e->data = (char *)doc->GetData(urlUtf8, &e->size); if (!e->data) { delete e; return false; } urlDataCache.Append(e); } *data = e->data; *len = e->size; return true; }
void VerticalLayout::Arrange(const Rect finalRect) { DirectionalLayoutData * e; SizeInfo * si; Vec<SizeInfo> sizes; for (e = els.IterStart(); e; e = els.IterNext()) { SizeInfo sizeInfo = { e->desiredSize.Height, e->sizeLayoutAxis, 0, 0 }; sizes.Append(sizeInfo); } RedistributeSizes(sizes.LendData(), sizes.Count(), finalRect.Height); for (e = els.IterStart(), si = sizes.IterStart(); e; e = els.IterNext(), si = sizes.IterNext()) { int dx = CalcScaledClippedSize(finalRect.Width, e->sizeNonLayoutAxis, e->desiredSize.Width); int x = e->alignNonLayoutAxis.CalcOffset(dx, finalRect.Width); e->element->Arrange(Rect(x, si->finalPos, dx, si->finalSize)); } }
Font *GetFont(const WCHAR *name, float size, FontStyle style) { Entry f = { (WCHAR *)name, size, style, NULL }; for (Entry *e = cache.IterStart(); e; e = cache.IterNext()) { if (f == *e) return e->font; } f.font = ::new Font(name, size, style); if (!f.font) { // fall back to the default font, if a desired font can't be created f.font = ::new Font(L"Times New Roman", size, style); if (!f.font) { if (cache.Count() > 0) return cache.At(0).font; return NULL; } } f.name = str::Dup(f.name); cache.Append(f); return f.font; }
void PaintForwardSearchMark(WindowInfo *win, HDC hdc) { PageInfo *pageInfo = win->dm->GetPageInfo(win->fwdSearchMark.page); if (!pageInfo || 0.0 == pageInfo->visibleRatio) return; // Draw the rectangles highlighting the forward search results Vec<RectI> rects; for (size_t i = 0; i < win->fwdSearchMark.rects.Count(); i++) { RectI rect = win->fwdSearchMark.rects.At(i); rect = win->dm->CvtToScreen(win->fwdSearchMark.page, rect.Convert<double>()); if (gUserPrefs->forwardSearch.highlightOffset > 0) { rect.x = max(pageInfo->pageOnScreen.x, 0) + (int)(gUserPrefs->forwardSearch.highlightOffset * win->dm->ZoomReal()); rect.dx = (int)((gUserPrefs->forwardSearch.highlightWidth > 0 ? gUserPrefs->forwardSearch.highlightWidth : 15.0) * win->dm->ZoomReal()); rect.y -= 4; rect.dy += 8; } rects.Append(rect); } BYTE alpha = (BYTE)(0x5f * 1.0f * (HIDE_FWDSRCHMARK_STEPS - win->fwdSearchMark.hideStep) / HIDE_FWDSRCHMARK_STEPS); PaintTransparentRectangles(hdc, win->canvasRc, rects, gUserPrefs->forwardSearch.highlightColor, alpha, 0); }
static void VecTest() { Vec<int> ints; assert(ints.Count() == 0); ints.Append(1); ints.Push(2); ints.InsertAt(0, -1); assert(ints.Count() == 3); assert(ints.At(0) == -1 && ints.At(1) == 1 && ints.At(2) == 2); assert(ints.At(0) == -1 && ints.Last() == 2); int last = ints.Pop(); assert(last == 2); assert(ints.Count() == 2); ints.Push(3); ints.RemoveAt(0); assert(ints.Count() == 2); assert(ints.At(0) == 1 && ints.At(1) == 3); ints.Reset(); assert(ints.Count() == 0); for (int i = 0; i < 1000; i++) { ints.Push(i); } assert(ints.Count() == 1000 && ints.At(500) == 500); ints.Remove(500); assert(ints.Count() == 999 && ints.At(500) == 501); last = ints.Pop(); assert(last == 999); ints.Append(last); assert(ints.AtPtr(501) == &ints.At(501)); { Vec<int> ints2(ints); assert(ints2.Count() == 999); assert(ints.LendData() != ints2.LendData()); ints.Remove(600); assert(ints.Count() < ints2.Count()); ints2 = ints; assert(ints2.Count() == 998); } { char buf[2] = {'a', '\0'}; str::Str<char> v(0); for (int i = 0; i < 7; i++) { v.Append(buf, 1); buf[0] = buf[0] + 1; } char *s = v.LendData(); assert(str::Eq("abcdefg", s)); assert(7 == v.Count()); v.Set("helo"); assert(4 == v.Count()); assert(str::Eq("helo", v.LendData())); } { str::Str<char> v(128); v.Append("boo", 3); assert(str::Eq("boo", v.LendData())); assert(v.Count() == 3); v.Append("fop"); assert(str::Eq("boofop", v.LendData())); assert(v.Count() == 6); v.RemoveAt(2, 3); assert(v.Count() == 3); assert(str::Eq("bop", v.LendData())); v.Append('a'); assert(v.Count() == 4); assert(str::Eq("bopa", v.LendData())); char *s = v.StealData(); assert(str::Eq("bopa", s)); free(s); assert(v.Count() == 0); } { str::Str<char> v(0); for (int i = 0; i < 32; i++) { assert(v.Count() == i * 6); v.Append("lambd", 5); if (i % 2 == 0) v.Append('a'); else v.Push('a'); } for (int i=1; i<=16; i++) { v.RemoveAt((16 - i) * 6, 6); assert(v.Count() == (32 - i) * 6); } v.RemoveAt(0, 6 * 15); assert(v.Count() == 6); char *s = v.LendData(); assert(str::Eq(s, "lambda")); s = v.StealData(); assert(str::Eq(s, "lambda")); free(s); assert(v.Count() == 0); v.Append("lambda"); assert(str::Eq(v.LendData(), "lambda")); char c = v.Pop(); assert(c == 'a'); assert(str::Eq(v.LendData(), "lambd")); } VecTestAppendFmt(); { Vec<PointI *> v; srand((unsigned int)time(NULL)); for (int i = 0; i < 128; i++) { v.Append(new PointI(i, i)); size_t pos = rand() % v.Count(); v.InsertAt(pos, new PointI(i, i)); } assert(v.Count() == 128 * 2); size_t idx = 0; for (PointI **p = v.IterStart(); p; p = v.IterNext()) { assert(idx == v.IterIdx()); ++idx; } while (v.Count() > 64) { size_t pos = rand() % v.Count(); PointI *f = v.At(pos); v.Remove(f); delete f; } DeleteVecMembers(v); } { Vec<int> v; v.Append(2); for (int i = 0; i < 500; i++) v.Append(4); v.At(250) = 5; v.Reverse(); assert(v.Count() == 501 && v.At(0) == 4 && v.At(249) == v.At(251) && v.At(250) == 5 && v.At(500) == 2); v.Remove(4); v.Reverse(); assert(v.Count() == 500 && v.At(0) == 2 && v.At(249) == v.At(251) && v.At(250) == 5 && v.At(499) == 4); } }
// see http://itexmac.sourceforge.net/pdfsync.html for the specification int Pdfsync::RebuildIndex() { size_t len; ScopedMem<char> data(file::ReadAll(syncfilepath, &len)); if (!data) return PDFSYNCERR_SYNCFILE_CANNOT_BE_OPENED; // convert the file data into a list of zero-terminated strings str::TransChars(data, "\r\n", "\0\0"); // parse preamble (jobname and version marker) char *line = data; char *dataEnd = data + len; // replace star by spaces (TeX uses stars instead of spaces in filenames) str::TransChars(line, "*/", " \\"); ScopedMem<WCHAR> jobName(str::conv::FromAnsi(line)); jobName.Set(str::Join(jobName, L".tex")); jobName.Set(PrependDir(jobName)); line = Advance0Line(line, dataEnd); UINT versionNumber = 0; if (!line || !str::Parse(line, "version %u", &versionNumber) || versionNumber != 1) return PDFSYNCERR_SYNCFILE_CANNOT_BE_OPENED; // reset synchronizer database srcfiles.Reset(); lines.Reset(); points.Reset(); fileIndex.Reset(); sheetIndex.Reset(); Vec<size_t> filestack; UINT page = 1; sheetIndex.Append(0); // add the initial tex file to the source file stack filestack.Push(srcfiles.Count()); srcfiles.Append(jobName.StealData()); PdfsyncFileIndex findex = { 0 }; fileIndex.Append(findex); PdfsyncLine psline; PdfsyncPoint pspoint; // parse data UINT maxPageNo = engine->PageCount(); while ((line = Advance0Line(line, dataEnd)) != NULL) { if (!line) break; switch (*line) { case 'l': psline.file = filestack.Last(); if (str::Parse(line, "l %u %u %u", &psline.record, &psline.line, &psline.column)) lines.Append(psline); else if (str::Parse(line, "l %u %u", &psline.record, &psline.line)) { psline.column = 0; lines.Append(psline); } // else dbg("Bad 'l' line in the pdfsync file"); break; case 's': if (str::Parse(line, "s %u", &page)) sheetIndex.Append(points.Count()); // else dbg("Bad 's' line in the pdfsync file"); // if (0 == page || page > maxPageNo) // dbg("'s' line with invalid page number in the pdfsync file"); break; case 'p': pspoint.page = page; if (0 == page || page > maxPageNo) /* ignore point for invalid page number */; else if (str::Parse(line, "p %u %u %u", &pspoint.record, &pspoint.x, &pspoint.y)) points.Append(pspoint); else if (str::Parse(line, "p* %u %u %u", &pspoint.record, &pspoint.x, &pspoint.y)) points.Append(pspoint); // else dbg("Bad 'p' line in the pdfsync file"); break; case '(': { ScopedMem<WCHAR> filename(str::conv::FromAnsi(line + 1)); // if the filename contains quotes then remove them // TODO: this should never happen!? if (filename[0] == '"' && filename[str::Len(filename) - 1] == '"') filename.Set(str::DupN(filename + 1, str::Len(filename) - 2)); // undecorate the filepath: replace * by space and / by \ str::TransChars(filename, L"*/", L" \\"); // if the file name extension is not specified then add the suffix '.tex' if (str::IsEmpty(path::GetExt(filename))) filename.Set(str::Join(filename, L".tex")); // ensure that the path is absolute if (PathIsRelative(filename)) filename.Set(PrependDir(filename)); filestack.Push(srcfiles.Count()); srcfiles.Append(filename.StealData()); findex.start = findex.end = lines.Count(); fileIndex.Append(findex); } break; case ')': if (filestack.Count() > 1) fileIndex.At(filestack.Pop()).end = lines.Count(); // else dbg("Unbalanced ')' line in the pdfsync file"); break; default: // dbg("Ignoring invalid pdfsync line starting with '%c'", *line); break; } } fileIndex.At(0).end = lines.Count(); assert(filestack.Count() == 1); return Synchronizer::RebuildIndex(); }
void OnMenuPrint(WindowInfo *win, bool waitForCompletion) { // we remember some printer settings per process static ScopedMem<DEVMODE> defaultDevMode; static PrintScaleAdv defaultScaleAdv = PrintScaleShrink; static bool defaultAsImage = false; static bool hasDefaults = false; if (!hasDefaults) { hasDefaults = true; defaultAsImage = gGlobalPrefs->printerDefaults.printAsImage; if (str::EqI(gGlobalPrefs->printerDefaults.printScale, "fit")) defaultScaleAdv = PrintScaleFit; else if (str::EqI(gGlobalPrefs->printerDefaults.printScale, "none")) defaultScaleAdv = PrintScaleNone; } bool printSelection = false; Vec<PRINTPAGERANGE> ranges; PRINTER_INFO_2 printerInfo = { 0 }; if (!HasPermission(Perm_PrinterAccess)) return; if (win->AsChm()) { // the Print dialog allows access to the file system, so fall back // to printing the entire document without dialog if that isn't desired bool showUI = HasPermission(Perm_DiskAccess); win->AsChm()->PrintCurrentPage(showUI); return; } if (win->AsEbook()) { // TODO: use EbookEngine for printing? return; } CrashIf(!win->AsFixed()); if (!win->AsFixed()) return; DisplayModel *dm = win->AsFixed(); #ifndef DISABLE_DOCUMENT_RESTRICTIONS if (!dm->engine()->AllowsPrinting()) return; #endif if (win->printThread) { int res = MessageBox(win->hwndFrame, _TR("Printing is still in progress. Abort and start over?"), _TR("Printing in progress."), MB_ICONEXCLAMATION | MB_YESNO | MbRtlReadingMaybe()); if (res == IDNO) return; } AbortPrinting(win); // the Print dialog allows access to the file system, so fall back // to printing the entire document without dialog if that isn't desired if (!HasPermission(Perm_DiskAccess)) { PrintFile(dm->engine()); return; } PRINTDLGEX pd; ZeroMemory(&pd, sizeof(PRINTDLGEX)); pd.lStructSize = sizeof(PRINTDLGEX); pd.hwndOwner = win->hwndFrame; pd.Flags = PD_USEDEVMODECOPIESANDCOLLATE | PD_COLLATE; if (!win->selectionOnPage) pd.Flags |= PD_NOSELECTION; pd.nCopies = 1; /* by default print all pages */ pd.nPageRanges = 1; pd.nMaxPageRanges = MAXPAGERANGES; PRINTPAGERANGE *ppr = AllocArray<PRINTPAGERANGE>(MAXPAGERANGES); pd.lpPageRanges = ppr; ppr->nFromPage = 1; ppr->nToPage = dm->PageCount(); pd.nMinPage = 1; pd.nMaxPage = dm->PageCount(); pd.nStartPage = START_PAGE_GENERAL; Print_Advanced_Data advanced(PrintRangeAll, defaultScaleAdv, defaultAsImage); ScopedMem<DLGTEMPLATE> dlgTemplate; // needed for RTL languages HPROPSHEETPAGE hPsp = CreatePrintAdvancedPropSheet(&advanced, dlgTemplate); pd.lphPropertyPages = &hPsp; pd.nPropertyPages = 1; // restore remembered settings if (defaultDevMode) { DEVMODE *p = defaultDevMode.Get(); pd.hDevMode = GlobalMemDup(p, p->dmSize + p->dmDriverExtra); } if (PrintDlgEx(&pd) != S_OK) { if (CommDlgExtendedError() != 0) { /* if PrintDlg was cancelled then CommDlgExtendedError is zero, otherwise it returns the error code, which we could look at here if we wanted. for now just warn the user that printing has stopped becasue of an error */ MessageBoxWarning(win->hwndFrame, _TR("Couldn't initialize printer"), _TR("Printing problem.")); } goto Exit; } if (pd.dwResultAction == PD_RESULT_PRINT || pd.dwResultAction == PD_RESULT_APPLY) { // remember settings for this process LPDEVMODE devMode = (LPDEVMODE)GlobalLock(pd.hDevMode); if (devMode) { defaultDevMode.Set((LPDEVMODE)memdup(devMode, devMode->dmSize + devMode->dmDriverExtra)); GlobalUnlock(pd.hDevMode); } defaultScaleAdv = advanced.scale; defaultAsImage = advanced.asImage; } if (pd.dwResultAction != PD_RESULT_PRINT) goto Exit; if (pd.Flags & PD_CURRENTPAGE) { PRINTPAGERANGE pr = { dm->CurrentPageNo(), dm->CurrentPageNo() }; ranges.Append(pr); } else if (win->selectionOnPage && (pd.Flags & PD_SELECTION)) { printSelection = true; } else if (!(pd.Flags & PD_PAGENUMS)) { PRINTPAGERANGE pr = { 1, dm->PageCount() }; ranges.Append(pr); } else { assert(pd.nPageRanges > 0); for (DWORD i = 0; i < pd.nPageRanges; i++) ranges.Append(pd.lpPageRanges[i]); } LPDEVNAMES devNames = (LPDEVNAMES)GlobalLock(pd.hDevNames); LPDEVMODE devMode = (LPDEVMODE)GlobalLock(pd.hDevMode); if (devNames) { printerInfo.pDriverName = (LPWSTR)devNames + devNames->wDriverOffset; printerInfo.pPrinterName = (LPWSTR)devNames + devNames->wDeviceOffset; printerInfo.pPortName = (LPWSTR)devNames + devNames->wOutputOffset; } PrintData *data = new PrintData(dm->engine(), &printerInfo, devMode, ranges, advanced, dm->GetRotation(), printSelection ? win->selectionOnPage : NULL); if (devNames) GlobalUnlock(pd.hDevNames); if (devMode) GlobalUnlock(pd.hDevMode); // if a file is missing and the engine can't thus be cloned, // we print using the original engine on the main thread // so that the document can't be closed and the original engine // unexpectedly deleted // TODO: instead prevent closing the document so that printing // can still happen on a separate thread and be interruptible bool failedEngineClone = dm->engine() && !data->engine; if (failedEngineClone) data->engine = dm->engine(); if (!waitForCompletion && !failedEngineClone) PrintToDeviceOnThread(win, data); else { PrintToDevice(*data); if (failedEngineClone) data->engine = NULL; delete data; } Exit: free(ppr); GlobalFree(pd.hDevNames); GlobalFree(pd.hDevMode); }
Vec<PageAnnotation> *LoadFileModifications(const WCHAR *filepath) { ScopedMem<WCHAR> modificationsPath(str::Join(filepath, SMX_FILE_EXT)); size_t len; ScopedMem<char> data(file::ReadAll(modificationsPath, &len)); if (!data) return NULL; CssPullParser parser(data, len); if (!parser.NextRule()) return NULL; const CssSelector *sel = parser.NextSelector(); if (!sel || !IsSelector(sel, "@meta") || parser.NextSelector()) return NULL; const CssProperty *prop = parser.NextProperty(); if (!prop || Css_Version != prop->type || !str::Parse(prop->s, prop->sLen, SMX_CURR_VERSION "%$")) return NULL; Vec<PageAnnotation> *list = new Vec<PageAnnotation>(); while (parser.NextRule()) { sel = parser.NextSelector(); if (!sel) continue; PageAnnotType type = IsSelector(sel, "highlight") ? Annot_Highlight : IsSelector(sel, "underline") ? Annot_Underline : IsSelector(sel, "strikeout") ? Annot_StrikeOut : IsSelector(sel, "squiggly") ? Annot_Squiggly : Annot_None; if (Annot_None == type || parser.NextSelector()) continue; int pageNo = 0; RectT<float> rect; PageAnnotation::Color color; while ((prop = parser.NextProperty())) { switch (prop->type) { case Css_Page: if (!str::Parse(prop->s, prop->sLen, "%d%$", &pageNo)) pageNo = 0; break; case Css_Rect: if (!str::Parse(prop->s, prop->sLen, "%f %f %f %f%$", &rect.x, &rect.y, &rect.dx, &rect.dy)) rect = RectT<float>(); break; case Css_Color: int r, g, b, a; if (str::Parse(prop->s, prop->sLen, "#%2x%2x%2x%2x%$", &a, &r, &g, &b)) color = PageAnnotation::Color(r, g, b, a); else if (str::Parse(prop->s, prop->sLen, "#%2x%2x%2x%$", &r, &g, &b)) color = PageAnnotation::Color(r, g, b); break; } } if (pageNo <= 0 || rect.IsEmpty()) continue; list->Append(PageAnnotation(type, pageNo, rect.Convert<double>(), color)); } return list; }
void OnMenuPrint(WindowInfo *win, bool waitForCompletion) { // we remember some printer settings per process static ScopedMem<DEVMODE> defaultDevMode; static PrintScaleAdv defaultScaleAdv = PrintScaleShrink; static bool defaultAsImage = false; bool printSelection = false; Vec<PRINTPAGERANGE> ranges; PRINTER_INFO_2 printerInfo = { 0 }; if (!HasPermission(Perm_PrinterAccess)) return; DisplayModel *dm = win->dm; assert(dm); if (!dm) return; if (!dm->engine || !dm->engine->AllowsPrinting()) return; if (win->IsChm()) { win->dm->AsChmEngine()->PrintCurrentPage(); return; } if (win->printThread) { int res = MessageBox(win->hwndFrame, _TR("Printing is still in progress. Abort and start over?"), _TR("Printing in progress."), MB_ICONEXCLAMATION | MB_YESNO | (IsUIRightToLeft() ? MB_RTLREADING : 0)); if (res == IDNO) return; } AbortPrinting(win); PRINTDLGEX pd; ZeroMemory(&pd, sizeof(PRINTDLGEX)); pd.lStructSize = sizeof(PRINTDLGEX); pd.hwndOwner = win->hwndFrame; pd.Flags = PD_USEDEVMODECOPIESANDCOLLATE | PD_COLLATE; if (!win->selectionOnPage) pd.Flags |= PD_NOSELECTION; pd.nCopies = 1; /* by default print all pages */ pd.nPageRanges = 1; pd.nMaxPageRanges = MAXPAGERANGES; PRINTPAGERANGE *ppr = AllocArray<PRINTPAGERANGE>(MAXPAGERANGES); pd.lpPageRanges = ppr; ppr->nFromPage = 1; ppr->nToPage = dm->PageCount(); pd.nMinPage = 1; pd.nMaxPage = dm->PageCount(); pd.nStartPage = START_PAGE_GENERAL; Print_Advanced_Data advanced(PrintRangeAll, defaultScaleAdv, defaultAsImage); ScopedMem<DLGTEMPLATE> dlgTemplate; // needed for RTL languages HPROPSHEETPAGE hPsp = CreatePrintAdvancedPropSheet(&advanced, dlgTemplate); pd.lphPropertyPages = &hPsp; pd.nPropertyPages = 1; // restore remembered settings if (defaultDevMode) pd.hDevMode = GlobalMemDup(defaultDevMode.Get(), defaultDevMode.Get()->dmSize + defaultDevMode.Get()->dmDriverExtra); if (PrintDlgEx(&pd) != S_OK) { if (CommDlgExtendedError() != 0) { /* if PrintDlg was cancelled then CommDlgExtendedError is zero, otherwise it returns the error code, which we could look at here if we wanted. for now just warn the user that printing has stopped becasue of an error */ MessageBox(win->hwndFrame, _TR("Couldn't initialize printer"), _TR("Printing problem."), MB_ICONEXCLAMATION | MB_OK | (IsUIRightToLeft() ? MB_RTLREADING : 0)); } goto Exit; } if (pd.dwResultAction == PD_RESULT_PRINT || pd.dwResultAction == PD_RESULT_APPLY) { // remember settings for this process LPDEVMODE devMode = (LPDEVMODE)GlobalLock(pd.hDevMode); if (devMode) { defaultDevMode.Set((LPDEVMODE)memdup(devMode, devMode->dmSize + devMode->dmDriverExtra)); GlobalUnlock(pd.hDevMode); } defaultScaleAdv = advanced.scale; defaultAsImage = advanced.asImage; } if (pd.dwResultAction != PD_RESULT_PRINT) goto Exit; if (pd.Flags & PD_CURRENTPAGE) { PRINTPAGERANGE pr = { dm->CurrentPageNo(), dm->CurrentPageNo() }; ranges.Append(pr); } else if (win->selectionOnPage && (pd.Flags & PD_SELECTION)) { printSelection = true; } else if (!(pd.Flags & PD_PAGENUMS)) { PRINTPAGERANGE pr = { 1, dm->PageCount() }; ranges.Append(pr); } else { assert(pd.nPageRanges > 0); for (DWORD i = 0; i < pd.nPageRanges; i++) ranges.Append(pd.lpPageRanges[i]); } LPDEVNAMES devNames = (LPDEVNAMES)GlobalLock(pd.hDevNames); LPDEVMODE devMode = (LPDEVMODE)GlobalLock(pd.hDevMode); if (devNames) { printerInfo.pDriverName = (LPWSTR)devNames + devNames->wDriverOffset; printerInfo.pPrinterName = (LPWSTR)devNames + devNames->wDeviceOffset; printerInfo.pPortName = (LPWSTR)devNames + devNames->wOutputOffset; } PrintData *data = new PrintData(dm->engine, &printerInfo, devMode, ranges, advanced, dm->Rotation(), printSelection ? win->selectionOnPage : NULL); if (devNames) GlobalUnlock(pd.hDevNames); if (devMode) GlobalUnlock(pd.hDevMode); if (!waitForCompletion) PrintToDeviceOnThread(win, data); else { PrintToDevice(*data); delete data; } Exit: free(ppr); GlobalFree(pd.hDevNames); GlobalFree(pd.hDevMode); }
Vec<PageElement *> *DjVuEngineImpl::GetElements(int pageNo) { assert(1 <= pageNo && pageNo <= PageCount()); if (annos && miniexp_dummy == annos[pageNo-1]) { ScopedCritSec scope(&gDjVuContext.lock); while ((annos[pageNo-1] = ddjvu_document_get_pageanno(doc, pageNo-1)) == miniexp_dummy) gDjVuContext.SpinMessageLoop(); } if (!annos || !annos[pageNo-1]) return NULL; ScopedCritSec scope(&gDjVuContext.lock); Vec<PageElement *> *els = new Vec<PageElement *>(); RectI page = PageMediabox(pageNo).Round(); ddjvu_status_t status; ddjvu_pageinfo_t info; while ((status = ddjvu_document_get_pageinfo(doc, pageNo-1, &info)) < DDJVU_JOB_OK) gDjVuContext.SpinMessageLoop(); float dpiFactor = 1.0; if (DDJVU_JOB_OK == status) dpiFactor = GetFileDPI() / info.dpi; miniexp_t *links = ddjvu_anno_get_hyperlinks(annos[pageNo-1]); for (int i = 0; links[i]; i++) { miniexp_t anno = miniexp_cdr(links[i]); miniexp_t url = miniexp_car(anno); const char *urlUtf8 = NULL; if (miniexp_stringp(url)) urlUtf8 = miniexp_to_str(url); else if (miniexp_consp(url) && miniexp_car(url) == miniexp_symbol("url") && miniexp_stringp(miniexp_cadr(url)) && miniexp_stringp(miniexp_caddr(url))) { urlUtf8 = miniexp_to_str(miniexp_cadr(url)); } if (!urlUtf8) continue; anno = miniexp_cdr(anno); miniexp_t comment = miniexp_car(anno); const char *commentUtf8 = NULL; if (miniexp_stringp(comment)) commentUtf8 = miniexp_to_str(comment); anno = miniexp_cdr(anno); miniexp_t area = miniexp_car(anno); miniexp_t type = miniexp_car(area); if (type != miniexp_symbol("rect") && type != miniexp_symbol("oval") && type != miniexp_symbol("text")) continue; // unsupported shape; area = miniexp_cdr(area); if (!miniexp_numberp(miniexp_car(area))) continue; int x = miniexp_to_int(miniexp_car(area)); area = miniexp_cdr(area); if (!miniexp_numberp(miniexp_car(area))) continue; int y = miniexp_to_int(miniexp_car(area)); area = miniexp_cdr(area); if (!miniexp_numberp(miniexp_car(area))) continue; int w = miniexp_to_int(miniexp_car(area)); area = miniexp_cdr(area); if (!miniexp_numberp(miniexp_car(area))) continue; int h = miniexp_to_int(miniexp_car(area)); area = miniexp_cdr(area); if (dpiFactor != 1.0) { x = (int)(x * dpiFactor); w = (int)(w * dpiFactor); y = (int)(y * dpiFactor); h = (int)(h * dpiFactor); } RectI rect(x, page.dy - y - h, w, h); ScopedMem<char> link(ResolveNamedDest(urlUtf8)); els->Append(new DjVuLink(pageNo, rect, link ? link : urlUtf8, commentUtf8)); } free(links); return els; }
/* Draws the about screen and remembers some state for hyperlinking. It transcribes the design I did in graphics software - hopeless to understand without seeing the design. */ static void DrawAbout(HWND hwnd, HDC hdc, RectI rect, Vec<StaticLinkInfo>& linkInfo) { HPEN penBorder = CreatePen(PS_SOLID, ABOUT_LINE_OUTER_SIZE, WIN_COL_BLACK); HPEN penDivideLine = CreatePen(PS_SOLID, ABOUT_LINE_SEP_SIZE, WIN_COL_BLACK); HPEN penLinkLine = CreatePen(PS_SOLID, ABOUT_LINE_SEP_SIZE, COL_BLUE_LINK); ScopedFont fontLeftTxt(GetSimpleFont(hdc, LEFT_TXT_FONT, LEFT_TXT_FONT_SIZE)); ScopedFont fontRightTxt(GetSimpleFont(hdc, RIGHT_TXT_FONT, RIGHT_TXT_FONT_SIZE)); HGDIOBJ origFont = SelectObject(hdc, fontLeftTxt); /* Just to remember the orig font */ ClientRect rc(hwnd); RECT rTmp = rc.ToRECT(); ScopedGdiObj<HBRUSH> brushAboutBg(CreateSolidBrush(GetAboutBgColor())); FillRect(hdc, &rTmp, brushAboutBg); /* render title */ RectI titleRect(rect.TL(), CalcSumatraVersionSize(hdc)); ScopedGdiObj<HBRUSH> bgBrush(CreateSolidBrush(GetLogoBgColor())); SelectObject(hdc, bgBrush); SelectObject(hdc, penBorder); #ifndef ABOUT_USE_LESS_COLORS Rectangle(hdc, rect.x, rect.y + ABOUT_LINE_OUTER_SIZE, rect.x + rect.dx, rect.y + titleRect.dy + ABOUT_LINE_OUTER_SIZE); #else RectI titleBgBand(0, rect.y, rc.dx, titleRect.dy); RECT rcLogoBg = titleBgBand.ToRECT(); FillRect(hdc, &rcLogoBg, bgBrush); PaintLine(hdc, RectI(0, rect.y, rc.dx, 0)); PaintLine(hdc, RectI(0, rect.y + titleRect.dy, rc.dx, 0)); #endif titleRect.Offset((rect.dx - titleRect.dx) / 2, 0); DrawSumatraVersion(hdc, titleRect); /* render attribution box */ SetTextColor(hdc, ABOUT_BORDER_COL); SetBkMode(hdc, TRANSPARENT); #ifndef ABOUT_USE_LESS_COLORS Rectangle(hdc, rect.x, rect.y + titleRect.dy, rect.x + rect.dx, rect.y + rect.dy); #endif /* render text on the left*/ SelectObject(hdc, fontLeftTxt); for (AboutLayoutInfoEl *el = gAboutLayoutInfo; el->leftTxt; el++) { TextOut(hdc, el->leftPos.x, el->leftPos.y, el->leftTxt, (int)str::Len(el->leftTxt)); } /* render text on the right */ SelectObject(hdc, fontRightTxt); SelectObject(hdc, penLinkLine); linkInfo.Reset(); for (AboutLayoutInfoEl *el = gAboutLayoutInfo; el->leftTxt; el++) { bool hasUrl = HasPermission(Perm_DiskAccess) && el->url; SetTextColor(hdc, hasUrl ? COL_BLUE_LINK : ABOUT_BORDER_COL); TextOut(hdc, el->rightPos.x, el->rightPos.y, el->rightTxt, (int)str::Len(el->rightTxt)); if (hasUrl) { int underlineY = el->rightPos.y + el->rightPos.dy - 3; PaintLine(hdc, RectI(el->rightPos.x, underlineY, el->rightPos.dx, 0)); linkInfo.Append(StaticLinkInfo(el->rightPos, el->url, el->url)); } } SelectObject(hdc, penDivideLine); RectI divideLine(gAboutLayoutInfo[0].rightPos.x - ABOUT_LEFT_RIGHT_SPACE_DX, rect.y + titleRect.dy + 4, 0, rect.y + rect.dy - 4 - gAboutLayoutInfo[0].rightPos.y); PaintLine(hdc, divideLine); SelectObject(hdc, origFont); DeleteObject(penBorder); DeleteObject(penDivideLine); DeleteObject(penLinkLine); }