From 1f9d7e7572a7bd85fd1e538e781b70acdfa39e63 Mon Sep 17 00:00:00 2001 From: XingfenD Date: Wed, 16 Sep 2026 15:22:59 +0800 Subject: [PATCH] feat(frontend): cbz page/spread view math (TDD) --- frontend/src/lib/cbzPages.ts | 21 +++++++++++++++++++++ frontend/test/cbzPages.test.ts | 24 ++++++++++++++++++++++++ 2 files changed, 45 insertions(+) create mode 100644 frontend/src/lib/cbzPages.ts create mode 100644 frontend/test/cbzPages.test.ts diff --git a/frontend/src/lib/cbzPages.ts b/frontend/src/lib/cbzPages.ts new file mode 100644 index 0000000..dff7109 --- /dev/null +++ b/frontend/src/lib/cbzPages.ts @@ -0,0 +1,21 @@ +export type PageMode = "page" | "spread"; + +export function viewCount(count: number, mode: PageMode): number { + return mode === "page" ? count : Math.ceil(count / 2); +} +export function pageIndexToView(i: number, mode: PageMode): number { + return mode === "page" ? i : Math.floor(i / 2); +} +export function viewToPageIndex(v: number, mode: PageMode): number { + return mode === "page" ? v : v * 2; +} +/** 一个 spread 视图要渲染的页,按显示顺序(LTR=[左,右];RTL=[右,左] 即先读页在前)。 */ +export function spreadPages(first: number, count: number, rtl: boolean): number[] { + const pages = [first, ...(first + 1 < count ? [first + 1] : [])]; + return rtl ? [...pages].reverse() : pages; +} +/** 翻一屏/页;rtl 时方向翻转;越界钳制。 */ +export function turnView(v: number, d: number, rtl: boolean, views: number): number { + const step = rtl ? -d : d; + return Math.max(0, Math.min(views - 1, v + step)); +} diff --git a/frontend/test/cbzPages.test.ts b/frontend/test/cbzPages.test.ts new file mode 100644 index 0000000..5a7162f --- /dev/null +++ b/frontend/test/cbzPages.test.ts @@ -0,0 +1,24 @@ +import { describe, expect, it } from "vitest"; +import { spreadPages, pageIndexToView, turnView, viewCount, viewToPageIndex } from "../src/lib/cbzPages"; + +describe("cbzPages", () => { + it("page mode: one page per view", () => { + expect(viewCount(5, "page")).toBe(5); + expect(viewToPageIndex(2, "page")).toBe(2); + expect(pageIndexToView(2, "page")).toBe(2); + }); + it("spread mode: pairs [0,1] [2,3] [4]", () => { + expect(viewCount(5, "spread")).toBe(3); + expect(pageIndexToView(3, "spread")).toBe(1); + expect(viewToPageIndex(1, "spread")).toBe(2); + expect(spreadPages(2, 5, false)).toEqual([2, 3]); + expect(spreadPages(4, 5, false)).toEqual([4]); // 落单页 + expect(spreadPages(2, 5, true)).toEqual([3, 2]); // RTL 右页为先 + }); + it("turnView clamps and RTL flips direction", () => { + expect(turnView(0, 1, false, 3)).toBe(1); + expect(turnView(2, 1, false, 3)).toBe(2); // 右边界钳制 + expect(turnView(0, -1, false, 3)).toBe(0); // 左边界钳制 + expect(turnView(1, 1, true, 3)).toBe(0); // RTL:d=+1 变后退 + }); +});