From 3f7b5dcff8c848a4835c2e66833da6f4869b6c26 Mon Sep 17 00:00:00 2001 From: Codex Date: Tue, 18 Aug 2026 18:09:59 +0800 Subject: [PATCH 1/5] Fix display indentation and add x64 package build --- .github/workflows/build-package.yml | 48 +++++++++++++++++++++++++++++ Reader/DisplaySet.cpp | 4 +++ Reader/OnlineBook.cpp | 2 +- Reader/Page.cpp | 7 +++-- 4 files changed, 58 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/build-package.yml diff --git a/.github/workflows/build-package.yml b/.github/workflows/build-package.yml new file mode 100644 index 0000000..b5af4f3 --- /dev/null +++ b/.github/workflows/build-package.yml @@ -0,0 +1,48 @@ +name: Build Reader package + +on: + push: + branches: + - codex/fix-security-and-stability + workflow_dispatch: + +jobs: + build-x64: + runs-on: windows-2022 + + steps: + - name: Check out source + uses: actions/checkout@v4 + + - name: Set up MSBuild + uses: microsoft/setup-msbuild@v2 + + - name: Build x64 Release + shell: pwsh + run: >- + msbuild Reader.sln /m /t:Rebuild + /p:Configuration=Release + /p:Platform=x64 + /p:PlatformToolset=v143 + /p:WindowsTargetPlatformVersion=10.0 + + - name: Package Reader + id: package + shell: pwsh + run: | + $shortSha = '${{ github.sha }}'.Substring(0, 7) + $packageName = "Reader_custom_x64_$shortSha" + $packageDir = Join-Path $env:RUNNER_TEMP $packageName + New-Item -ItemType Directory -Force $packageDir | Out-Null + Copy-Item -LiteralPath 'x64\Release\Reader.exe' -Destination $packageDir + Copy-Item -LiteralPath 'readme.txt' -Destination $packageDir + New-Item -ItemType Directory -Force 'dist' | Out-Null + & '.\tool\7z.exe' a "dist\$packageName.7z" "$packageDir\Reader.exe" "$packageDir\readme.txt" + "name=$packageName" >> $env:GITHUB_OUTPUT + + - name: Upload package + uses: actions/upload-artifact@v4 + with: + name: ${{ steps.package.outputs.name }} + path: dist/*.7z + if-no-files-found: error diff --git a/Reader/DisplaySet.cpp b/Reader/DisplaySet.cpp index ccef339..166d3a8 100644 --- a/Reader/DisplaySet.cpp +++ b/Reader/DisplaySet.cpp @@ -211,12 +211,15 @@ static INT_PTR CALLBACK DisplaySetDlgProc(HWND hDlg, UINT message, WPARAM wParam case IDC_CHECK_MENU_FONT: res = (int)SendMessage(GetDlgItem(hDlg, IDC_CHECK_MENU_FONT), BM_GETCHECK, 0, NULL); _display.meun_font_follow = BST_CHECKED == res ? 1 : 0; + break; case IDC_CHECK_WORD_WRAP: res = (int)SendMessage(GetDlgItem(hDlg, IDC_CHECK_WORD_WRAP), BM_GETCHECK, 0, NULL); _display.word_wrap = BST_CHECKED == res ? 1 : 0; + break; case IDC_CHECK_INDENT: res = (int)SendMessage(GetDlgItem(hDlg, IDC_CHECK_INDENT), BM_GETCHECK, 0, NULL); _display.line_indent = BST_CHECKED == res ? 1 : 0; + break; case IDC_CHECK_BLANKLINES: res = (int)SendMessage(GetDlgItem(hDlg, IDC_CHECK_BLANKLINES), BM_GETCHECK, 0, NULL); _display.blank_lines = BST_CHECKED == res ? 1 : 0; @@ -224,6 +227,7 @@ static INT_PTR CALLBACK DisplaySetDlgProc(HWND hDlg, UINT message, WPARAM wParam case IDC_CHECK_CHAPTER_PAGE: res = (int)SendMessage(GetDlgItem(hDlg, IDC_CHECK_CHAPTER_PAGE), BM_GETCHECK, 0, NULL); _display.chapter_page = BST_CHECKED == res ? 1 : 0; + break; default: break; } diff --git a/Reader/OnlineBook.cpp b/Reader/OnlineBook.cpp index 460e801..6cc8e6b 100644 --- a/Reader/OnlineBook.cpp +++ b/Reader/OnlineBook.cpp @@ -1397,7 +1397,7 @@ BOOL OnlineBook::ParseOlHeader(ol_header_t* header) item.size = cinfo->size; item.title = (TCHAR*)(buf + cinfo->title_offset); item.url = buf + cinfo->url_offset; - item.title_len = cinfo->title_offset/2; + item.title_len = (int)item.title.size(); m_Chapters.push_back(item); } diff --git a/Reader/Page.cpp b/Reader/Page.cpp index bfc9290..0155a5e 100644 --- a/Reader/Page.cpp +++ b/Reader/Page.cpp @@ -860,8 +860,11 @@ int Page::ParagraphToLines(HDC hdc, int start, int length, int width, int height _continue: if (is_blank(m_Text[i])) { - // for indent, ignore the blank chars which at the beginning of a paragraph. - if (LINE_INDENT && char_start == i && !is_title && is_new_paragraph && line_start == start) + // Normalize the leading whitespace of a paragraph. LINE_INDENT + // controls the layout offset instead of preserving whitespace from + // the source text, so disabling it produces a genuinely flush-left + // paragraph. + if (char_start == i && !is_title && is_new_paragraph && line_start == start) { char_start = i + 1; word_start = i + 1; From 77508583ccfb7de7af89a4147f577b2364af8426 Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 18 Aug 2026 19:39:08 +0800 Subject: [PATCH 2/5] Allow hotkey-only window hiding --- README.md | 2 +- Reader/Reader.cpp | 43 +++++++++++++++---------------------------- 2 files changed, 16 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index fc441e7..8f8e433 100644 --- a/README.md +++ b/README.md @@ -219,7 +219,7 @@ Reader 是我个人开发的一款绿色、开源、免费的阅读器软件, 4. 增加隐藏任务栏图标功能(此功能暂不支持windows xp及以下系统) 4.1 默认不开启此功能 4.2 开启方式:菜单栏 > setting > config > 窗口设置 > 勾选“隐藏任务栏图标” - 4.3 勾选此功能,会强制勾选“最小化托盘”,方便用户点击打开窗口 + 4.3 此功能可与“最小化托盘”独立设置;若同时关闭托盘,请使用热键显示窗口 5. 支持窗口背景全透明,而字体不透明功能。 5.1 注意:此功能暂时只能在“隐藏边框 或者 全屏模式”下支持 5.2 新增 Ctrl + Shift + 鼠标滚动:直接设置最高/最低透明度。方便快速背景全透明 diff --git a/Reader/Reader.cpp b/Reader/Reader.cpp index 2145ff6..44185b5 100644 --- a/Reader/Reader.cpp +++ b/Reader/Reader.cpp @@ -329,9 +329,14 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) OnFindText(hWnd, message, wParam, lParam); return 0; } - if (WM_TASKBAR_CREATED == message) - { - ShowSysTray(hWnd, TRUE); + if (WM_TASKBAR_CREATED == message) + { + if (_header->show_systray) + { + // Explorer discarded the old icon, so force it to be added again. + _nid.uFlags = 0; + ShowSysTray(hWnd, TRUE); + } } switch (message) { @@ -1478,17 +1483,10 @@ INT_PTR CALLBACK Setting(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) else cid = IDC_RADIO_MODE3; SendMessage(GetDlgItem(hDlg, cid), BM_SETCHECK, BST_CHECKED, NULL); - WheelSpeedInit(hDlg); - // init window style - SendMessage(GetDlgItem(hDlg, IDC_CHECK_TRAY), BM_SETCHECK, _header->show_systray ? BST_CHECKED : BST_UNCHECKED, NULL); - if (_header->hide_taskbar) - { - SendMessage(GetDlgItem(hDlg, IDC_CHECK_TASKBAR), BM_SETCHECK, BST_CHECKED, NULL); - SendMessage(GetDlgItem(hDlg, IDC_CHECK_TRAY), BM_SETCHECK, BST_CHECKED, NULL); - EnableWindow(GetDlgItem(hDlg, IDC_CHECK_TRAY), FALSE); - } - else - SendMessage(GetDlgItem(hDlg, IDC_CHECK_TASKBAR), BM_SETCHECK, BST_UNCHECKED, NULL); + WheelSpeedInit(hDlg); + // init window style + SendMessage(GetDlgItem(hDlg, IDC_CHECK_TRAY), BM_SETCHECK, _header->show_systray ? BST_CHECKED : BST_UNCHECKED, NULL); + SendMessage(GetDlgItem(hDlg, IDC_CHECK_TASKBAR), BM_SETCHECK, _header->hide_taskbar ? BST_CHECKED : BST_UNCHECKED, NULL); SendMessage(GetDlgItem(hDlg, IDC_CHECK_LRHIDE), BM_SETCHECK, _header->disable_lrhide ? BST_UNCHECKED : BST_CHECKED, NULL); SendMessage(GetDlgItem(hDlg, IDC_CHECK_ESCHIDE), BM_SETCHECK, _header->disable_eschide ? BST_UNCHECKED : BST_CHECKED, NULL); if ((_header->autopage_mode & 0x0f) == apm_page) @@ -1575,20 +1573,9 @@ INT_PTR CALLBACK Setting(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) EndDialog(hDlg, LOWORD(wParam)); return (INT_PTR)TRUE; break; - case IDC_CHECK_TASKBAR: - res = SendMessage(GetDlgItem(hDlg, IDC_CHECK_TASKBAR), BM_GETCHECK, 0, NULL); - if (res == BST_CHECKED) - { - SendMessage(GetDlgItem(hDlg, IDC_CHECK_TRAY), BM_SETCHECK, BST_CHECKED, NULL); - EnableWindow(GetDlgItem(hDlg, IDC_CHECK_TRAY), FALSE); - } - else - { - EnableWindow(GetDlgItem(hDlg, IDC_CHECK_TRAY), TRUE); - } - break; - case IDC_CHECK_TRAY: - break; + case IDC_CHECK_TASKBAR: + case IDC_CHECK_TRAY: + break; default: break; } From 7253d875daccc4823b0d3d4783a144aeb4ab56be Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 18 Aug 2026 19:45:03 +0800 Subject: [PATCH 3/5] Keep hidden taskbar state after Explorer restart --- Reader/Reader.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Reader/Reader.cpp b/Reader/Reader.cpp index 44185b5..e2b3532 100644 --- a/Reader/Reader.cpp +++ b/Reader/Reader.cpp @@ -331,6 +331,9 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) } if (WM_TASKBAR_CREATED == message) { + // Explorer may recreate taskbar buttons for visible top-level windows. + // Reapply the user's taskbar preference before restoring any tray icon. + ShowInTaskbar(hWnd, !_header->hide_taskbar); if (_header->show_systray) { // Explorer discarded the old icon, so force it to be added again. From 301d6dfe8536f3e9c85b2e6575f72e4777550e21 Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 19 Aug 2026 10:01:08 +0800 Subject: [PATCH 4/5] Fix missing quotation marks in transparent mode --- Reader/Page.cpp | 38 ++++++++++++++++++++++++++++++-------- Reader/Page.h | 2 ++ 2 files changed, 32 insertions(+), 8 deletions(-) diff --git a/Reader/Page.cpp b/Reader/Page.cpp index 0155a5e..f488c7c 100644 --- a/Reader/Page.cpp +++ b/Reader/Page.cpp @@ -468,7 +468,7 @@ void Page::DrawAlphaText(HDC hdc, char_info_t* p_char, int x, int y, int h, alph BYTE FillR,FillG,FillB,ThisA; BYTE *DataPtr; BOOL is_tag = FALSE; - int i,j; + int i,j,left,right; extern BYTE _textAlpha; is_tag = p_char->dc_idx >= 2; @@ -477,9 +477,9 @@ void Page::DrawAlphaText(HDC hdc, char_info_t* p_char, int x, int y, int h, alph if (!is_tag) { - SetTextColor(hdc, 0x00FFFFFF); - SetBkColor(hdc, 0x00000000); - SetBkMode(hdc, OPAQUE); + SetTextColor(hdc, 0x00FFFFFF); + SetBkColor(hdc, 0x00000000); + SetBkMode(hdc, TRANSPARENT); FillR = GetRValue(m_dcList[p_char->dc_idx].TextColor); FillG = GetGValue(m_dcList[p_char->dc_idx].TextColor); @@ -489,10 +489,17 @@ void Page::DrawAlphaText(HDC hdc, char_info_t* p_char, int x, int y, int h, alph // draw text TextOut(hdc, x, y + (h - p_char->cy), &m_Text[p_char->idx], 1); + // Process the glyph's actual black box instead of only its advance width. + // Small punctuation such as U+201C can extend to the left of the origin. + left = is_tag ? x : x + p_char->abc_a - 1; + right = is_tag ? x + p_char->cx : x + p_char->abc_a + p_char->abc_b + 1; + left = max(0, left); + right = min(p_alpha_dc->width, right); + // convert pixel for (i = p_alpha_dc->height - y - (h - p_char->cy) - 1; i >= p_alpha_dc->height - y - h; i--) { - for (j = x; j < x + p_char->cx; j++) + for (j = left; j < right; j++) { DataPtr = &p_alpha_dc->pvBits[(i * p_alpha_dc->width + j) * 4]; @@ -513,8 +520,12 @@ void Page::DrawAlphaText(HDC hdc, char_info_t* p_char, int x, int y, int h, alph m_BlankPage = FALSE; } - ThisA = *DataPtr; // Move alpha and pre-multiply with RGB - *DataPtr++ = (FillB * ThisA * _textAlpha) >> 16; + // ClearType stores coverage in separate RGB subpixels. Using + // only the blue channel makes thin glyphs disappear. + ThisA = max(*DataPtr, max(*(DataPtr + 1), *(DataPtr + 2))); + if (ThisA == 0 && *(DataPtr + 3) != 0) + continue; // keep an overlapping glyph converted earlier + *DataPtr++ = (FillB * ThisA * _textAlpha) >> 16; *DataPtr++ = (FillG * ThisA * _textAlpha) >> 16; *DataPtr++ = (FillR * ThisA * _textAlpha) >> 16; *DataPtr++ = (ThisA * _textAlpha) >> 8; // Set text Alpha @@ -737,6 +748,7 @@ int Page::ParagraphToLines(HDC hdc, int start, int length, int width, int height int is_title = IsChapter(start); int indent_width = GetIndentWidth(hdc); SIZE sz; + ABC abc; int x, y, w, h; int char_start, line_start, word_start; int line_len, char_len, word_height, word_width; // for WORD_WRAP @@ -767,6 +779,16 @@ int Page::ParagraphToLines(HDC hdc, int start, int length, int width, int height chars[i - start].dc_idx = m_dcIndex; chars[i - start].cx = sz.cx; chars[i - start].cy = sz.cy; + if (GetCharABCWidths(hdc, m_Text[i], m_Text[i], &abc)) + { + chars[i - start].abc_a = abc.abcA; + chars[i - start].abc_b = abc.abcB; + } + else + { + chars[i - start].abc_a = 0; + chars[i - start].abc_b = sz.cx; + } if (i == start && x == indent_width && x + sz.cx > width) { @@ -1615,4 +1637,4 @@ int Page::GetTextBeginIndex(void) Gdiplus::Bitmap* Page::GetCover(void) { return NULL; -} \ No newline at end of file +} diff --git a/Reader/Page.h b/Reader/Page.h index c00ba63..a8e9772 100644 --- a/Reader/Page.h +++ b/Reader/Page.h @@ -10,6 +10,8 @@ typedef struct char_info_t int dc_idx; int cx; int cy; + int abc_a; + int abc_b; } char_info_t; typedef struct line_info_t From 474d8fe498ebcf6ca85ba787f23f44037a1fba95 Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 19 Aug 2026 10:13:11 +0800 Subject: [PATCH 5/5] Preserve HTML entities in book text --- Reader/EpubBook.cpp | 7 +++++-- Reader/MobiBook.cpp | 5 +++-- Reader/Page.cpp | 38 ++++++++------------------------------ Reader/Page.h | 2 -- 4 files changed, 16 insertions(+), 36 deletions(-) diff --git a/Reader/EpubBook.cpp b/Reader/EpubBook.cpp index 45d1e1c..47dfbb0 100644 --- a/Reader/EpubBook.cpp +++ b/Reader/EpubBook.cpp @@ -705,7 +705,10 @@ BOOL EpubBook::ParserOps(file_data_t *fdata, wchar_t **text, int *len, wchar_t * } xmlKeepBlanksDefault(1); - doc = xmlReadMemory((const char *)format_str, size, NULL, NULL, XML_PARSE_RECOVER/* | XML_PARSE_NOBLANKS*/); + // htmlDocDumpMemoryFormat may serialize characters such as curly quotes as + // HTML named entities. Reparse the formatted document as HTML so those + // entities survive instead of being discarded by the XML parser. + doc = htmlReadMemory((const char *)format_str, size, NULL, NULL, HTML_PARSE_RECOVER); xmlFree(format_str); if (!doc) goto end; @@ -1048,4 +1051,4 @@ BOOL EpubBook::ParserCover(epub_t &epub) } return m_Cover != NULL; -} \ No newline at end of file +} diff --git a/Reader/MobiBook.cpp b/Reader/MobiBook.cpp index 6d54bb1..53b489e 100644 --- a/Reader/MobiBook.cpp +++ b/Reader/MobiBook.cpp @@ -637,11 +637,12 @@ BOOL MobiBook::ParserOps(file_data_t *fdata, wchar_t **text, int *len, wchar_t * } xmlKeepBlanksDefault(1); - doc = xmlReadMemory((const char *)format_str, size, NULL, NULL, XML_PARSE_RECOVER | XML_PARSE_HUGE /*| XML_PARSE_NOBLANKS */ ); //XML_PARSE_HUGE 大文件支持 + // htmlDocDumpMemoryFormat can emit HTML named entities. The XML parser only + // understands XML's predefined entities and would silently lose others. + doc = htmlReadMemory((const char *)format_str, size, NULL, NULL, HTML_PARSE_RECOVER); xmlFree(format_str); if (!doc) goto end; - xmlDocDumpFormatMemory(doc, &format_str, &size, 1); #endif xpathctx = xmlXPathNewContext(doc); diff --git a/Reader/Page.cpp b/Reader/Page.cpp index f488c7c..0155a5e 100644 --- a/Reader/Page.cpp +++ b/Reader/Page.cpp @@ -468,7 +468,7 @@ void Page::DrawAlphaText(HDC hdc, char_info_t* p_char, int x, int y, int h, alph BYTE FillR,FillG,FillB,ThisA; BYTE *DataPtr; BOOL is_tag = FALSE; - int i,j,left,right; + int i,j; extern BYTE _textAlpha; is_tag = p_char->dc_idx >= 2; @@ -477,9 +477,9 @@ void Page::DrawAlphaText(HDC hdc, char_info_t* p_char, int x, int y, int h, alph if (!is_tag) { - SetTextColor(hdc, 0x00FFFFFF); - SetBkColor(hdc, 0x00000000); - SetBkMode(hdc, TRANSPARENT); + SetTextColor(hdc, 0x00FFFFFF); + SetBkColor(hdc, 0x00000000); + SetBkMode(hdc, OPAQUE); FillR = GetRValue(m_dcList[p_char->dc_idx].TextColor); FillG = GetGValue(m_dcList[p_char->dc_idx].TextColor); @@ -489,17 +489,10 @@ void Page::DrawAlphaText(HDC hdc, char_info_t* p_char, int x, int y, int h, alph // draw text TextOut(hdc, x, y + (h - p_char->cy), &m_Text[p_char->idx], 1); - // Process the glyph's actual black box instead of only its advance width. - // Small punctuation such as U+201C can extend to the left of the origin. - left = is_tag ? x : x + p_char->abc_a - 1; - right = is_tag ? x + p_char->cx : x + p_char->abc_a + p_char->abc_b + 1; - left = max(0, left); - right = min(p_alpha_dc->width, right); - // convert pixel for (i = p_alpha_dc->height - y - (h - p_char->cy) - 1; i >= p_alpha_dc->height - y - h; i--) { - for (j = left; j < right; j++) + for (j = x; j < x + p_char->cx; j++) { DataPtr = &p_alpha_dc->pvBits[(i * p_alpha_dc->width + j) * 4]; @@ -520,12 +513,8 @@ void Page::DrawAlphaText(HDC hdc, char_info_t* p_char, int x, int y, int h, alph m_BlankPage = FALSE; } - // ClearType stores coverage in separate RGB subpixels. Using - // only the blue channel makes thin glyphs disappear. - ThisA = max(*DataPtr, max(*(DataPtr + 1), *(DataPtr + 2))); - if (ThisA == 0 && *(DataPtr + 3) != 0) - continue; // keep an overlapping glyph converted earlier - *DataPtr++ = (FillB * ThisA * _textAlpha) >> 16; + ThisA = *DataPtr; // Move alpha and pre-multiply with RGB + *DataPtr++ = (FillB * ThisA * _textAlpha) >> 16; *DataPtr++ = (FillG * ThisA * _textAlpha) >> 16; *DataPtr++ = (FillR * ThisA * _textAlpha) >> 16; *DataPtr++ = (ThisA * _textAlpha) >> 8; // Set text Alpha @@ -748,7 +737,6 @@ int Page::ParagraphToLines(HDC hdc, int start, int length, int width, int height int is_title = IsChapter(start); int indent_width = GetIndentWidth(hdc); SIZE sz; - ABC abc; int x, y, w, h; int char_start, line_start, word_start; int line_len, char_len, word_height, word_width; // for WORD_WRAP @@ -779,16 +767,6 @@ int Page::ParagraphToLines(HDC hdc, int start, int length, int width, int height chars[i - start].dc_idx = m_dcIndex; chars[i - start].cx = sz.cx; chars[i - start].cy = sz.cy; - if (GetCharABCWidths(hdc, m_Text[i], m_Text[i], &abc)) - { - chars[i - start].abc_a = abc.abcA; - chars[i - start].abc_b = abc.abcB; - } - else - { - chars[i - start].abc_a = 0; - chars[i - start].abc_b = sz.cx; - } if (i == start && x == indent_width && x + sz.cx > width) { @@ -1637,4 +1615,4 @@ int Page::GetTextBeginIndex(void) Gdiplus::Bitmap* Page::GetCover(void) { return NULL; -} +} \ No newline at end of file diff --git a/Reader/Page.h b/Reader/Page.h index a8e9772..c00ba63 100644 --- a/Reader/Page.h +++ b/Reader/Page.h @@ -10,8 +10,6 @@ typedef struct char_info_t int dc_idx; int cx; int cy; - int abc_a; - int abc_b; } char_info_t; typedef struct line_info_t