papilot 0.0.0-stage → 0.1.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.
Files changed (99) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +126 -2
  3. package/README.zh-CN.md +127 -0
  4. package/bin/papilot.mjs +113 -0
  5. package/dist/apple-touch-icon.png +0 -0
  6. package/dist/assets/Inconsolatazi4-Regular-Dbtq7t_r.otf +0 -0
  7. package/dist/assets/TeXGyrePagellaX-Bold-CEtYdOXj.otf +0 -0
  8. package/dist/assets/TeXGyrePagellaX-Italic-CSuYIstn.otf +0 -0
  9. package/dist/assets/TeXGyrePagellaX-Regular-DQJjZE_L.otf +0 -0
  10. package/dist/assets/cursor-editorTextHighlight-kG3uUOwL.svg +8 -0
  11. package/dist/assets/index-BhQFCMTx.js +131 -0
  12. package/dist/assets/index-CAV3-87U.css +1 -0
  13. package/dist/assets/pdf-view-DJo0X38w.js +598 -0
  14. package/dist/assets/pdf.worker.min-CjEcRF4W.mjs +28 -0
  15. package/dist/favicon.png +0 -0
  16. package/dist/index.html +4 -0
  17. package/examples/papilot-paper/.latex-review.json +4 -0
  18. package/examples/papilot-paper/appendix/proofs.tex +60 -0
  19. package/examples/papilot-paper/appendix/reference.tex +54 -0
  20. package/examples/papilot-paper/main.tex +57 -0
  21. package/examples/papilot-paper/preamble.tex +53 -0
  22. package/examples/papilot-paper/references.bib +158 -0
  23. package/examples/papilot-paper/sections/01-introduction.tex +66 -0
  24. package/examples/papilot-paper/sections/02-using.tex +134 -0
  25. package/examples/papilot-paper/sections/03-background.tex +97 -0
  26. package/examples/papilot-paper/sections/04-model.tex +185 -0
  27. package/examples/papilot-paper/sections/05-conflicts.tex +99 -0
  28. package/examples/papilot-paper/sections/06-building.tex +70 -0
  29. package/examples/papilot-paper/sections/07-geometry.tex +193 -0
  30. package/examples/papilot-paper/sections/08-notes.tex +104 -0
  31. package/examples/papilot-paper/sections/09-agents.tex +106 -0
  32. package/examples/papilot-paper/sections/10-usage.tex +56 -0
  33. package/examples/papilot-paper/sections/11-git.tex +62 -0
  34. package/examples/papilot-paper/sections/12-security.tex +41 -0
  35. package/examples/papilot-paper/sections/13-evaluation.tex +106 -0
  36. package/examples/papilot-paper/sections/14-related.tex +38 -0
  37. package/examples/papilot-paper/sections/15-conclusion.tex +27 -0
  38. package/package.json +85 -4
  39. package/scripts/demo.ts +21 -0
  40. package/scripts/service.ts +35 -0
  41. package/src/notes.ts +40 -0
  42. package/src/server/agents.ts +343 -0
  43. package/src/server/codex.ts +63 -0
  44. package/src/server/frontend.ts +15 -0
  45. package/src/server/main.ts +202 -0
  46. package/src/server/manuscript.ts +337 -0
  47. package/src/server/paths.ts +10 -0
  48. package/src/server/pdf.ts +27 -0
  49. package/src/server/review.ts +53 -0
  50. package/src/server/security.ts +15 -0
  51. package/src/server/store.ts +60 -0
  52. package/src/server/workspace.ts +529 -0
  53. package/src/shared.ts +28 -0
  54. package/vendor/latex-review/LICENSE +21 -0
  55. package/vendor/latex-review/dist/web/assets/TeXGyrePagellaX-Bold-CEtYdOXj.otf +0 -0
  56. package/vendor/latex-review/dist/web/assets/TeXGyrePagellaX-Regular-DQJjZE_L.otf +0 -0
  57. package/vendor/latex-review/dist/web/assets/index-DyzYnIGk.css +1 -0
  58. package/vendor/latex-review/dist/web/assets/index-HxBK-dls.js +19 -0
  59. package/vendor/latex-review/dist/web/index.html +18 -0
  60. package/vendor/latex-review/package-lock.json +3114 -0
  61. package/vendor/latex-review/package.json +49 -0
  62. package/vendor/latex-review/src/cli.ts +494 -0
  63. package/vendor/latex-review/src/client.ts +198 -0
  64. package/vendor/latex-review/src/core/build.ts +296 -0
  65. package/vendor/latex-review/src/core/changes.ts +126 -0
  66. package/vendor/latex-review/src/core/config.ts +270 -0
  67. package/vendor/latex-review/src/core/diff.ts +54 -0
  68. package/vendor/latex-review/src/core/files.ts +96 -0
  69. package/vendor/latex-review/src/core/git.ts +317 -0
  70. package/vendor/latex-review/src/core/outline.ts +314 -0
  71. package/vendor/latex-review/src/core/process.ts +111 -0
  72. package/vendor/latex-review/src/core/publish.ts +339 -0
  73. package/vendor/latex-review/src/core/review.ts +734 -0
  74. package/vendor/latex-review/src/core/store.ts +66 -0
  75. package/vendor/latex-review/src/core/tasks.ts +237 -0
  76. package/vendor/latex-review/src/render/inline.ts +805 -0
  77. package/vendor/latex-review/src/render/locate.ts +481 -0
  78. package/vendor/latex-review/src/render/pdf.ts +281 -0
  79. package/vendor/latex-review/src/render/pdfdiff.ts +272 -0
  80. package/vendor/latex-review/src/render/round.ts +236 -0
  81. package/vendor/latex-review/src/render/worker.ts +10 -0
  82. package/vendor/latex-review/src/runtime.ts +23 -0
  83. package/vendor/latex-review/src/server.ts +401 -0
  84. package/vendor/latex-review/src/types.ts +283 -0
  85. package/vendor/latex-review/src/web/ChangeMap.tsx +212 -0
  86. package/vendor/latex-review/src/web/PdfStrip.tsx +77 -0
  87. package/vendor/latex-review/src/web/fonts/GUST-FONT-LICENSE.txt +29 -0
  88. package/vendor/latex-review/src/web/fonts/LPPL.txt +415 -0
  89. package/vendor/latex-review/src/web/fonts/MANIFEST-newpx.txt +20 -0
  90. package/vendor/latex-review/src/web/fonts/README-newpx.txt +473 -0
  91. package/vendor/latex-review/src/web/fonts/README.md +12 -0
  92. package/vendor/latex-review/src/web/fonts/TeXGyrePagellaX-Bold.otf +0 -0
  93. package/vendor/latex-review/src/web/fonts/TeXGyrePagellaX-Regular.otf +0 -0
  94. package/vendor/latex-review/src/web/index.html +17 -0
  95. package/vendor/latex-review/src/web/main.tsx +10 -0
  96. package/vendor/latex-review/src/web/model.ts +153 -0
  97. package/vendor/latex-review/src/web/overview.tsx +146 -0
  98. package/vendor/latex-review/src/web/review.tsx +991 -0
  99. package/vendor/latex-review/src/web/style.css +1249 -0
@@ -0,0 +1,283 @@
1
+ export type Span = [number, number];
2
+ export type Rect = [number, number, number, number];
3
+ export type SourceBox = [
4
+ page: number,
5
+ top: number,
6
+ bottom: number,
7
+ left?: number,
8
+ right?: number,
9
+ ];
10
+ export type Ranges = Record<string, Span[]>;
11
+ export type Op = [
12
+ "equal" | "replace" | "insert" | "delete",
13
+ number,
14
+ number,
15
+ number,
16
+ number,
17
+ ];
18
+ export interface Heading {
19
+ id: string;
20
+ title: string;
21
+ number: string;
22
+ kind: string;
23
+ level: number;
24
+ file: string;
25
+ line: number;
26
+ parent: string | null;
27
+ labels: string[];
28
+ ranges: Ranges;
29
+ files: string[];
30
+ }
31
+ export interface SectionRef {
32
+ id: string;
33
+ number: string;
34
+ title: string;
35
+ }
36
+ export interface Scope {
37
+ paths: string[];
38
+ ranges: Ranges | null;
39
+ sections: SectionRef[];
40
+ }
41
+ export interface Task {
42
+ name: string;
43
+ title: string;
44
+ worktree: string;
45
+ base: string;
46
+ accepted: string;
47
+ scope: Scope;
48
+ created: number;
49
+ closed: number | null;
50
+ }
51
+ export interface Unit {
52
+ id: string;
53
+ file: string;
54
+ status: "added" | "deleted" | "modified";
55
+ kind: "text" | "figure" | "binary";
56
+ whole_file: boolean;
57
+ ops: Op[];
58
+ old_start: number;
59
+ old_end: number;
60
+ new_start: number;
61
+ new_end: number;
62
+ old_text: string;
63
+ new_text: string;
64
+ truncated: boolean;
65
+ }
66
+ export interface Round {
67
+ task: string;
68
+ number: number;
69
+ before: string;
70
+ after: string;
71
+ units: Unit[];
72
+ summary: { warnings: string[]; targets: string[]; builds: string[] };
73
+ created: number;
74
+ }
75
+ export type ActionKind = "accept" | "reject" | "comment";
76
+ export type Decision = "accepted" | "rejected" | "commented" | "pending";
77
+ export interface ActionDetail {
78
+ accepted_before?: string;
79
+ accepted_after?: string;
80
+ not_reverted?: string[];
81
+ }
82
+ export interface Action {
83
+ seq: number;
84
+ id: string;
85
+ task: string;
86
+ round: number;
87
+ kind: ActionKind;
88
+ changes: string[];
89
+ body: string;
90
+ status: "applying" | "applied" | "undoing" | "undone" | "failed";
91
+ detail: ActionDetail;
92
+ created: number;
93
+ }
94
+ export interface Write {
95
+ action: string;
96
+ phase: "apply" | "undo";
97
+ path: string;
98
+ expected: Buffer | null;
99
+ target: Buffer | null;
100
+ }
101
+ export interface MessageData {
102
+ changes?: string[];
103
+ not_reverted?: string[];
104
+ undid?: string;
105
+ message?: number;
106
+ }
107
+ export interface Message {
108
+ id: number;
109
+ task: string;
110
+ round: number | null;
111
+ kind: ActionKind | "undo" | "note";
112
+ action: string | null;
113
+ body: string;
114
+ data: MessageData;
115
+ created: number;
116
+ }
117
+ export interface JobResult {
118
+ outcome?: "published" | "unchanged" | "empty";
119
+ round?: number | null;
120
+ changes?: number;
121
+ kinds?: Record<string, number>;
122
+ warnings?: string[];
123
+ error?: string;
124
+ details?: string[];
125
+ next?: string;
126
+ log?: string;
127
+ }
128
+ export interface Job {
129
+ id: string;
130
+ task: string;
131
+ status: "queued" | "running" | "done" | "failed";
132
+ phase: string;
133
+ result: JobResult;
134
+ created: number;
135
+ finished?: number;
136
+ }
137
+ export interface BuildInfo {
138
+ key: string;
139
+ tree: string;
140
+ targets: string[];
141
+ root: string;
142
+ cwd: string;
143
+ pdf: string;
144
+ seconds: number;
145
+ warnings: string[];
146
+ }
147
+ export interface Position {
148
+ page: number;
149
+ clip: Rect;
150
+ label: string;
151
+ }
152
+ export interface Mark {
153
+ kind: "removed" | "added" | "moved";
154
+ rect: Rect;
155
+ }
156
+ export interface Pair {
157
+ old: Position | null;
158
+ new: Position | null;
159
+ before_marks: Mark[];
160
+ after_marks: Mark[];
161
+ }
162
+ export type Visual = "unmapped" | "text" | "layout" | "position" | "identical";
163
+ export interface Card {
164
+ id: string;
165
+ file: string;
166
+ pairs: Pair[];
167
+ comparison: Visual;
168
+ own?: { old: SourceBox[]; new: SourceBox[] };
169
+ context_node?: string | null;
170
+ }
171
+ export interface TextGlyph {
172
+ text: string;
173
+ rect: Rect;
174
+ }
175
+ export interface TextLine {
176
+ text: string;
177
+ chars: TextGlyph[];
178
+ }
179
+ export interface Strip {
180
+ src: string;
181
+ clean: string;
182
+ width: number;
183
+ height: number;
184
+ page: number;
185
+ label: string;
186
+ text?: TextLine[];
187
+ }
188
+ export interface ChangeBlock {
189
+ kind: "change";
190
+ units: string[];
191
+ identical: boolean;
192
+ reflow?: boolean;
193
+ before: Strip[];
194
+ after: Strip[];
195
+ }
196
+ export type Block =
197
+ ChangeBlock | { kind: "context"; image: Strip } | { kind: "gap" };
198
+ export interface VisualUnit {
199
+ visual: Visual;
200
+ pages: string[];
201
+ section: string | null;
202
+ }
203
+ export interface RenderedRound {
204
+ units: Record<string, VisualUnit>;
205
+ sections: { node: string; number: string; title: string; blocks: Block[] }[];
206
+ loose: ChangeBlock[];
207
+ }
208
+ export interface RenderRequest {
209
+ main: string;
210
+ source_dir: string;
211
+ figures: string[];
212
+ old: BuildInfo;
213
+ new: BuildInfo;
214
+ units: Unit[];
215
+ }
216
+ export interface History {
217
+ round: number;
218
+ change: string;
219
+ comment: string;
220
+ new_text: string;
221
+ }
222
+ export interface RoundDetail extends Omit<RenderedRound, "units"> {
223
+ task: string;
224
+ round: number;
225
+ before: string;
226
+ after: string;
227
+ units: (Unit & Partial<VisualUnit> & { history?: History[] })[];
228
+ warnings: string[];
229
+ files: string;
230
+ }
231
+ export type TaskState =
232
+ "editing" | "building" | "build_failed" | "reviewing" | "revise" | "done";
233
+ export interface Summary {
234
+ task: string;
235
+ title: string;
236
+ state: TaskState;
237
+ round: number | null;
238
+ counts: Record<Decision, number>;
239
+ next: string;
240
+ }
241
+ export interface PageState {
242
+ csrf: string;
243
+ task: string;
244
+ title: string;
245
+ closed: boolean;
246
+ /** Optional host-provided reason that file decisions or comments are unavailable. */
247
+ decisionLock?: string | null;
248
+ commentLock?: string | null;
249
+ state: TaskState | null;
250
+ latest: number | null;
251
+ round: number | null;
252
+ revision: string | null;
253
+ decisions: Record<string, Decision>;
254
+ comments: Record<string, string>;
255
+ undo: { id: string; kind: ActionKind; changes: string[] } | null;
256
+ job:
257
+ | (Pick<Job, "id" | "status" | "phase" | "created"> &
258
+ JobResult & { ahead: number })
259
+ | null;
260
+ }
261
+ export interface FeedbackItem {
262
+ id: number;
263
+ kind: Message["kind"];
264
+ round: number | null;
265
+ text?: string;
266
+ changes?:
267
+ | string[]
268
+ | { id: string; file: string; lines: Span; old: string; new: string }[];
269
+ not_reverted?: string[];
270
+ undid?: string;
271
+ }
272
+ export interface Feedback extends Summary {
273
+ feedback: FeedbackItem[];
274
+ cursor: number;
275
+ build_error?: JobResult;
276
+ warnings?: string[];
277
+ }
278
+ export interface TaskInfo extends Summary {
279
+ worktree: string;
280
+ url: string;
281
+ viewing: boolean;
282
+ scope: { sections: string[]; files: string[] };
283
+ }
@@ -0,0 +1,212 @@
1
+ import { useEffect, useRef, useState, type RefObject } from "react";
2
+ import type { Decision } from "../types.ts";
3
+ import { stopState, type Stop } from "./model.ts";
4
+
5
+ type Region = { top: number; height: number };
6
+
7
+ export function ChangeMap({
8
+ scrollRoot,
9
+ stops,
10
+ locations,
11
+ decisions,
12
+ current,
13
+ disabled,
14
+ onSelect,
15
+ }: {
16
+ scrollRoot: RefObject<HTMLElement | null>;
17
+ stops: Stop[];
18
+ locations: string[];
19
+ decisions: Record<string, Decision>;
20
+ current: number;
21
+ disabled: boolean;
22
+ onSelect: (index: number) => void;
23
+ }) {
24
+ const rail = useRef<HTMLElement>(null),
25
+ drag = useRef<{ pointer: number; offset: number } | null>(null),
26
+ [regions, setRegions] = useState<Region[]>([]),
27
+ [viewport, setViewport] = useState<Region>({ top: 0, height: 0 });
28
+
29
+ useEffect(() => {
30
+ const root = scrollRoot.current,
31
+ track = rail.current;
32
+ if (!root || !track) return;
33
+ let frame = 0,
34
+ needsMeasure = true;
35
+ const update = () => {
36
+ frame = 0;
37
+ const extent = Math.max(root.scrollHeight, 1),
38
+ trackHeight = track.clientHeight,
39
+ maxScroll = Math.max(0, extent - root.clientHeight),
40
+ thumbHeight = Math.min(
41
+ trackHeight,
42
+ Math.max(8, (root.clientHeight / extent) * trackHeight),
43
+ );
44
+ setViewport({
45
+ top: maxScroll
46
+ ? (Math.max(0, Math.min(root.scrollTop, maxScroll)) / maxScroll) *
47
+ (trackHeight - thumbHeight)
48
+ : 0,
49
+ height: thumbHeight,
50
+ });
51
+ if (!needsMeasure) return;
52
+ needsMeasure = false;
53
+ const origin = root.getBoundingClientRect().top + root.clientTop;
54
+ setRegions(
55
+ [...root.querySelectorAll<HTMLElement>("[data-stop]")].map(
56
+ (article) => {
57
+ const box = article.getBoundingClientRect(),
58
+ top = Math.max(
59
+ 0,
60
+ ((box.top - origin + root.scrollTop) / extent) * trackHeight,
61
+ );
62
+ return {
63
+ top,
64
+ height: Math.max(
65
+ 0,
66
+ Math.min(
67
+ trackHeight - top,
68
+ Math.max(4, (box.height / extent) * trackHeight),
69
+ ),
70
+ ),
71
+ };
72
+ },
73
+ ),
74
+ );
75
+ };
76
+ const schedule = () => {
77
+ if (!frame) frame = requestAnimationFrame(update);
78
+ };
79
+ const observer = new ResizeObserver(() => {
80
+ needsMeasure = true;
81
+ schedule();
82
+ });
83
+ observer.observe(root);
84
+ observer.observe(track);
85
+ for (const child of root.children) observer.observe(child);
86
+ for (const article of root.querySelectorAll("[data-stop]"))
87
+ observer.observe(article);
88
+ // Banners can move the document without changing its own dimensions.
89
+ const mutations = new MutationObserver((records) => {
90
+ for (const record of records) {
91
+ for (const child of record.removedNodes)
92
+ if (child instanceof Element) observer.unobserve(child);
93
+ for (const child of record.addedNodes)
94
+ if (child instanceof Element) observer.observe(child);
95
+ }
96
+ needsMeasure = true;
97
+ schedule();
98
+ });
99
+ mutations.observe(root, { childList: true });
100
+ root.addEventListener("scroll", schedule, { passive: true });
101
+ schedule();
102
+ return () => {
103
+ observer.disconnect();
104
+ mutations.disconnect();
105
+ root.removeEventListener("scroll", schedule);
106
+ cancelAnimationFrame(frame);
107
+ };
108
+ }, [scrollRoot, stops]);
109
+
110
+ if (!stops.length) return null;
111
+
112
+ function scrollAt(clientY: number, offset: number) {
113
+ const root = scrollRoot.current,
114
+ track = rail.current;
115
+ if (!root || !track) return;
116
+ const y = clientY - track.getBoundingClientRect().top - track.clientTop,
117
+ travel = Math.max(1, track.clientHeight - viewport.height);
118
+ root.scrollTo({
119
+ top: ((y - offset) / travel) * (root.scrollHeight - root.clientHeight),
120
+ behavior: "instant",
121
+ });
122
+ }
123
+
124
+ return (
125
+ <nav
126
+ ref={rail}
127
+ className="change-map"
128
+ aria-label="Change map"
129
+ onPointerDown={(event) => {
130
+ if (
131
+ event.button !== 0 ||
132
+ (event.target as HTMLElement).closest("button")
133
+ )
134
+ return;
135
+ event.preventDefault();
136
+ const offset = (event.target as HTMLElement).classList.contains(
137
+ "change-map-viewport",
138
+ )
139
+ ? event.clientY -
140
+ event.currentTarget.getBoundingClientRect().top -
141
+ event.currentTarget.clientTop -
142
+ viewport.top
143
+ : viewport.height / 2;
144
+ drag.current = { pointer: event.pointerId, offset };
145
+ event.currentTarget.setPointerCapture(event.pointerId);
146
+ scrollAt(event.clientY, offset);
147
+ }}
148
+ onPointerMove={(event) => {
149
+ if (drag.current?.pointer === event.pointerId)
150
+ scrollAt(event.clientY, drag.current.offset);
151
+ }}
152
+ onPointerUp={(event) => {
153
+ if (drag.current?.pointer === event.pointerId) {
154
+ drag.current = null;
155
+ event.currentTarget.releasePointerCapture(event.pointerId);
156
+ }
157
+ }}
158
+ onLostPointerCapture={() => {
159
+ drag.current = null;
160
+ }}
161
+ >
162
+ <div
163
+ className="change-map-viewport"
164
+ style={{ top: viewport.top, height: viewport.height }}
165
+ aria-hidden="true"
166
+ />
167
+ {stops.map((stop, index) => {
168
+ const region = regions[index];
169
+ if (!region) return null;
170
+ const state = stopState(stop.units, decisions),
171
+ label = `Change ${index + 1} · ${state} · ${locations[index]}`;
172
+ return (
173
+ <button
174
+ key={stop.key}
175
+ type="button"
176
+ className="change-map-mark"
177
+ data-stop-index={index}
178
+ data-state={state}
179
+ aria-label={label}
180
+ aria-current={index === current ? "location" : undefined}
181
+ tabIndex={index === Math.max(0, current) ? 0 : -1}
182
+ disabled={disabled}
183
+ style={{ top: region.top, height: region.height }}
184
+ onClick={() => onSelect(index)}
185
+ onKeyDown={(event) => {
186
+ const target =
187
+ event.key === "ArrowDown"
188
+ ? index + 1
189
+ : event.key === "ArrowUp"
190
+ ? index - 1
191
+ : event.key === "Home"
192
+ ? 0
193
+ : event.key === "End"
194
+ ? stops.length - 1
195
+ : null;
196
+ if (target === null) return;
197
+ event.preventDefault();
198
+ event.stopPropagation();
199
+ rail.current
200
+ ?.querySelector<HTMLButtonElement>(
201
+ `[data-stop-index="${Math.max(0, Math.min(stops.length - 1, target))}"]`,
202
+ )
203
+ ?.focus();
204
+ }}
205
+ >
206
+ <span className="change-map-tooltip">{label}</span>
207
+ </button>
208
+ );
209
+ })}
210
+ </nav>
211
+ );
212
+ }
@@ -0,0 +1,77 @@
1
+ import { memo, useMemo, type CSSProperties } from "react";
2
+ import type { Strip, TextGlyph } from "../types.ts";
3
+ let context: CanvasRenderingContext2D | null | undefined;
4
+ const widths = new Map<string, number>();
5
+ function width(text: string) {
6
+ if (widths.has(text)) return widths.get(text)!;
7
+ context ??= document.createElement("canvas").getContext("2d");
8
+ if (!context) return 1;
9
+ context.font = '100px "Times New Roman", serif';
10
+ const measured = context.measureText(text).width / 100;
11
+ widths.set(text, measured);
12
+ return measured || 1;
13
+ }
14
+ function style(glyph: TextGlyph, image: Strip): CSSProperties {
15
+ const [x0, y0, x1, y1] = glyph.rect,
16
+ height = Math.max(y1 - y0, 0.1),
17
+ scaleX = (x1 - x0) / (height * width(glyph.text));
18
+ return {
19
+ left: `${(100 * x0) / image.width}%`,
20
+ top: `${(100 * y0) / image.height}%`,
21
+ fontSize: `${(100 * height) / image.width}cqw`,
22
+ transform: `scaleX(${scaleX})`,
23
+ };
24
+ }
25
+ export const PdfStrip = memo(function PdfStrip({
26
+ image,
27
+ base,
28
+ label,
29
+ clean = false,
30
+ }: {
31
+ image: Strip;
32
+ base: string;
33
+ label: string;
34
+ clean?: boolean;
35
+ }) {
36
+ const text = useMemo(
37
+ () =>
38
+ image.text?.map((line, i) => (
39
+ <span className="pdf-text-line" key={i}>
40
+ {line.chars.map((glyph, j) => (
41
+ <span key={j} className="pdf-glyph" style={style(glyph, image)}>
42
+ {glyph.text}
43
+ </span>
44
+ ))}
45
+ {"\n"}
46
+ </span>
47
+ )),
48
+ [image],
49
+ );
50
+ return (
51
+ <div
52
+ className="pdf-strip"
53
+ data-page={image.label}
54
+ style={{ aspectRatio: `${image.width}/${image.height}` }}
55
+ >
56
+ <img
57
+ src={base + (clean ? image.clean : image.src)}
58
+ alt={image.text?.length ? "" : label}
59
+ width={image.width}
60
+ height={image.height}
61
+ loading="lazy"
62
+ decoding="async"
63
+ draggable={false}
64
+ aria-hidden={Boolean(image.text?.length)}
65
+ />
66
+ {image.text?.length ? (
67
+ <div
68
+ className="pdf-text"
69
+ role="region"
70
+ aria-label={`${label}, PDF page ${image.label}`}
71
+ >
72
+ {text}
73
+ </div>
74
+ ) : null}
75
+ </div>
76
+ );
77
+ });
@@ -0,0 +1,29 @@
1
+ % This is a preliminary version (2006-09-30), barring acceptance from
2
+ % the LaTeX Project Team and other feedback, of the GUST Font License.
3
+ % (GUST is the Polish TeX Users Group, http://www.gust.org.pl)
4
+ %
5
+ % For the most recent version of this license see
6
+ % http://www.gust.org.pl/fonts/licenses/GUST-FONT-LICENSE.txt
7
+ % or
8
+ % http://tug.org/fonts/licenses/GUST-FONT-LICENSE.txt
9
+ %
10
+ % This work may be distributed and/or modified under the conditions
11
+ % of the LaTeX Project Public License, either version 1.3c of this
12
+ % license or (at your option) any later version.
13
+ %
14
+ % Please also observe the following clause:
15
+ % 1) it is requested, but not legally required, that derived works be
16
+ % distributed only after changing the names of the fonts comprising this
17
+ % work and given in an accompanying "manifest", and that the
18
+ % files comprising the Work, as listed in the manifest, also be given
19
+ % new names. Any exceptions to this request are also given in the
20
+ % manifest.
21
+ %
22
+ % We recommend the manifest be given in a separate file named
23
+ % MANIFEST-<fontid>.txt, where <fontid> is some unique identification
24
+ % of the font family. If a separate "readme" file accompanies the Work,
25
+ % we recommend a name of the form README-<fontid>.txt.
26
+ %
27
+ % The latest version of the LaTeX Project Public License is in
28
+ % http://www.latex-project.org/lppl.txt and version 1.3c or later
29
+ % is part of all distributions of LaTeX version 2006/05/20 or later.