@likecoin/epub-ts 0.5.1 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +45 -77
- package/dist/epub.cjs +3 -3
- package/dist/epub.cjs.map +1 -1
- package/dist/epub.js +705 -485
- package/dist/epub.js.map +1 -1
- package/dist/epub.node.cjs +3 -3
- package/dist/epub.node.cjs.map +1 -1
- package/dist/epub.node.js +701 -483
- package/dist/epub.node.js.map +1 -1
- package/dist/epub.umd.js +3 -3
- package/dist/epub.umd.js.map +1 -1
- package/dist/managers/default/index.d.ts +2 -0
- package/dist/managers/views/iframe.d.ts +1 -0
- package/dist/mapping.d.ts +22 -5
- package/dist/utils/text-measurer.d.ts +97 -0
- package/package.json +6 -2
|
@@ -2,6 +2,7 @@ import { default as Mapping } from '../../mapping';
|
|
|
2
2
|
import { default as Queue } from '../../utils/queue';
|
|
3
3
|
import { default as Stage } from '../helpers/stage';
|
|
4
4
|
import { default as Views } from '../helpers/views';
|
|
5
|
+
import { default as TextMeasurer } from '../../utils/text-measurer';
|
|
5
6
|
import { default as Layout } from '../../layout';
|
|
6
7
|
import { default as Section } from '../../section';
|
|
7
8
|
import { default as Contents } from '../../contents';
|
|
@@ -52,6 +53,7 @@ declare class DefaultViewManager implements IEventEmitter<DefaultManagerEvents>
|
|
|
52
53
|
overflow: string;
|
|
53
54
|
layout: Layout;
|
|
54
55
|
mapping: Mapping;
|
|
56
|
+
_measurer: TextMeasurer;
|
|
55
57
|
location: ViewLocation[];
|
|
56
58
|
isPaginated: boolean;
|
|
57
59
|
scrollLeft: number;
|
|
@@ -52,6 +52,7 @@ declare class IframeView implements IEventEmitter<IframeViewEvents> {
|
|
|
52
52
|
_textHeight: number | undefined;
|
|
53
53
|
_contentWidth: number | undefined;
|
|
54
54
|
_contentHeight: number | undefined;
|
|
55
|
+
_contentDirty: boolean;
|
|
55
56
|
_needsReframe: boolean;
|
|
56
57
|
_expanding: boolean;
|
|
57
58
|
elementBounds: {
|
package/dist/mapping.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { EpubCFIPair, RangePair, LayoutProps } from './types';
|
|
2
2
|
import { default as IframeView } from './managers/views/iframe';
|
|
3
3
|
import { default as Contents } from './contents';
|
|
4
|
+
import { default as TextMeasurer } from './utils/text-measurer';
|
|
4
5
|
/**
|
|
5
6
|
* Map text locations to CFI ranges
|
|
6
7
|
* @class
|
|
@@ -14,7 +15,8 @@ declare class Mapping {
|
|
|
14
15
|
horizontal: boolean;
|
|
15
16
|
direction: string;
|
|
16
17
|
_dev: boolean;
|
|
17
|
-
|
|
18
|
+
_measurer: TextMeasurer | null;
|
|
19
|
+
constructor(layout: LayoutProps, direction?: string, axis?: string, dev?: boolean, measurer?: TextMeasurer);
|
|
18
20
|
/**
|
|
19
21
|
* Find CFI pairs for entire section at once
|
|
20
22
|
*/
|
|
@@ -54,24 +56,39 @@ declare class Mapping {
|
|
|
54
56
|
* @return {Range}
|
|
55
57
|
*/
|
|
56
58
|
findEnd(root: Node, start: number, end: number): Range;
|
|
59
|
+
/**
|
|
60
|
+
* Try to prepare a text node's root for canvas-based measurement.
|
|
61
|
+
* Returns the PreparedNode for this text node, or null if not available.
|
|
62
|
+
* @private
|
|
63
|
+
*/
|
|
64
|
+
private _canvasPrepare;
|
|
65
|
+
/**
|
|
66
|
+
* Canvas fast path: use binary search on pre-measured cumulative widths
|
|
67
|
+
* to find a Range at the target position, then verify with one getBoundingClientRect.
|
|
68
|
+
* Returns the Range if verification passes, or null to fall through to DOM loop.
|
|
69
|
+
* @private
|
|
70
|
+
*/
|
|
71
|
+
private _canvasFindRange;
|
|
57
72
|
/**
|
|
58
73
|
* Find Text Start Range
|
|
59
74
|
* @private
|
|
60
|
-
* @param {Node}
|
|
75
|
+
* @param {Node} node text node
|
|
61
76
|
* @param {number} start position to start at
|
|
62
77
|
* @param {number} end position to end at
|
|
78
|
+
* @param {DOMRect} [nodePos] pre-computed node bounds from findStart (avoids redundant reflow)
|
|
63
79
|
* @return {Range}
|
|
64
80
|
*/
|
|
65
|
-
findTextStartRange(node: Node, start: number, end: number): Range;
|
|
81
|
+
findTextStartRange(node: Node, start: number, end: number, nodePos?: DOMRect): Range;
|
|
66
82
|
/**
|
|
67
83
|
* Find Text End Range
|
|
68
84
|
* @private
|
|
69
|
-
* @param {Node}
|
|
85
|
+
* @param {Node} node text node
|
|
70
86
|
* @param {number} start position to start at
|
|
71
87
|
* @param {number} end position to end at
|
|
88
|
+
* @param {DOMRect} [nodePos] pre-computed node bounds from findEnd (avoids redundant reflow)
|
|
72
89
|
* @return {Range}
|
|
73
90
|
*/
|
|
74
|
-
findTextEndRange(node: Node, start: number, end: number): Range;
|
|
91
|
+
findTextEndRange(node: Node, start: number, end: number, nodePos?: DOMRect): Range;
|
|
75
92
|
/**
|
|
76
93
|
* Split up a text node into ranges for each word
|
|
77
94
|
* @private
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canvas-based text measurement utility.
|
|
3
|
+
*
|
|
4
|
+
* Applies pretext's prepare/layout pattern: expensive measurement is done once
|
|
5
|
+
* via CanvasRenderingContext2D.measureText(), then layout queries (finding the
|
|
6
|
+
* character offset at a pixel position) are pure binary-search arithmetic with
|
|
7
|
+
* zero DOM reflow.
|
|
8
|
+
*
|
|
9
|
+
* Browser-only — not imported by the Node.js entry point.
|
|
10
|
+
*/
|
|
11
|
+
export interface TextSegment {
|
|
12
|
+
/** The text node this segment belongs to */
|
|
13
|
+
node: Text;
|
|
14
|
+
/** Character offset within the text node where this segment starts */
|
|
15
|
+
charOffset: number;
|
|
16
|
+
/** The segment text content */
|
|
17
|
+
text: string;
|
|
18
|
+
/** Measured width of this segment in pixels */
|
|
19
|
+
width: number;
|
|
20
|
+
/** Cumulative width from the start of this text node's segment list */
|
|
21
|
+
cumWidth: number;
|
|
22
|
+
}
|
|
23
|
+
export interface PreparedNode {
|
|
24
|
+
node: Text;
|
|
25
|
+
segments: TextSegment[];
|
|
26
|
+
totalWidth: number;
|
|
27
|
+
font: string;
|
|
28
|
+
}
|
|
29
|
+
declare class TextMeasurer {
|
|
30
|
+
private _canvas;
|
|
31
|
+
private _ctx;
|
|
32
|
+
/** font string → (text → width), bounded to MAX_WIDTH_CACHE_FONTS entries */
|
|
33
|
+
private _widthCache;
|
|
34
|
+
/** parent element → prepared nodes */
|
|
35
|
+
private _preparedCache;
|
|
36
|
+
/** text node → prepared node, for O(1) lookup in _canvasPrepare */
|
|
37
|
+
private _nodeIndex;
|
|
38
|
+
/** shared Intl.Segmenter instance (lazy) */
|
|
39
|
+
private _segmenter;
|
|
40
|
+
private getCanvas;
|
|
41
|
+
private getSegmenter;
|
|
42
|
+
/**
|
|
43
|
+
* Measure a text string with a given CSS font, returning its width in pixels.
|
|
44
|
+
* Results are cached per font+text pair.
|
|
45
|
+
*/
|
|
46
|
+
measureText(text: string, font: string): number;
|
|
47
|
+
/**
|
|
48
|
+
* Segment text into word-level pieces suitable for measurement.
|
|
49
|
+
* Uses Intl.Segmenter when available, falls back to space-splitting
|
|
50
|
+
* (with per-character splitting for CJK).
|
|
51
|
+
*/
|
|
52
|
+
segmentText(text: string): {
|
|
53
|
+
text: string;
|
|
54
|
+
index: number;
|
|
55
|
+
}[];
|
|
56
|
+
/**
|
|
57
|
+
* Prepare phase: measure all text nodes under a root element.
|
|
58
|
+
* Returns PreparedNode[] with cumulative widths for binary search.
|
|
59
|
+
*
|
|
60
|
+
* Text nodes whose parent has exotic CSS (letter-spacing, word-spacing,
|
|
61
|
+
* text-indent) are skipped — the caller should fall back to DOM Range
|
|
62
|
+
* measurement for those.
|
|
63
|
+
*
|
|
64
|
+
* @param root The container element (usually document.body)
|
|
65
|
+
* @param win The window object for getComputedStyle
|
|
66
|
+
* @returns PreparedNode[] with entries for measurable text nodes (may be empty)
|
|
67
|
+
*/
|
|
68
|
+
prepare(root: Element, win: Window): PreparedNode[];
|
|
69
|
+
/**
|
|
70
|
+
* Layout phase: find the segment index at a given pixel position
|
|
71
|
+
* using binary search on cumulative widths.
|
|
72
|
+
*
|
|
73
|
+
* @param segments The TextSegment[] from a PreparedNode
|
|
74
|
+
* @param position Target position in pixels (relative to text node start)
|
|
75
|
+
* @returns Index into the segments array
|
|
76
|
+
*/
|
|
77
|
+
findSegmentIndex(segments: TextSegment[], position: number): number;
|
|
78
|
+
/**
|
|
79
|
+
* Look up a previously prepared text node in O(1).
|
|
80
|
+
* Returns null if the node was not prepared (exotic CSS, not yet prepared, etc.).
|
|
81
|
+
*/
|
|
82
|
+
getPreparedNode(node: Text): PreparedNode | null;
|
|
83
|
+
/**
|
|
84
|
+
* Check if a text node's parent has exotic CSS that prevents canvas measurement.
|
|
85
|
+
*/
|
|
86
|
+
hasExoticCSS(node: Text, win: Window): boolean;
|
|
87
|
+
/**
|
|
88
|
+
* Invalidate cached preparation for a root element,
|
|
89
|
+
* including all per-node index entries under it.
|
|
90
|
+
*/
|
|
91
|
+
invalidate(root: Element): void;
|
|
92
|
+
/**
|
|
93
|
+
* Destroy the measurer, releasing the canvas and all caches.
|
|
94
|
+
*/
|
|
95
|
+
destroy(): void;
|
|
96
|
+
}
|
|
97
|
+
export default TextMeasurer;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@likecoin/epub-ts",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.6.0",
|
|
4
|
+
"description": "Drop-in epubjs replacement — fully typed, 1 dependency, actively maintained",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"epub",
|
|
7
7
|
"ebook",
|
|
@@ -76,6 +76,10 @@
|
|
|
76
76
|
"optional": true
|
|
77
77
|
}
|
|
78
78
|
},
|
|
79
|
+
"publishConfig": {
|
|
80
|
+
"access": "public",
|
|
81
|
+
"provenance": true
|
|
82
|
+
},
|
|
79
83
|
"dependencies": {
|
|
80
84
|
"jszip": "^3.10.1"
|
|
81
85
|
}
|