kviewer 0.2.0 → 0.2.2
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 +47 -0
- package/dist/module.d.mts +8 -1
- package/dist/module.json +1 -1
- package/dist/module.mjs +25 -3
- package/dist/runtime/annotation/engine/canvas-resolution.d.ts +7 -0
- package/dist/runtime/annotation/engine/canvas-resolution.js +21 -0
- package/dist/runtime/annotation/engine/editor/editor.d.ts +5 -0
- package/dist/runtime/annotation/engine/editor/editor.js +14 -0
- package/dist/runtime/annotation/engine/editor/selector.d.ts +13 -1
- package/dist/runtime/annotation/engine/editor/selector.js +60 -8
- package/dist/runtime/annotation/engine/painter.d.ts +31 -0
- package/dist/runtime/annotation/engine/painter.js +88 -2
- package/dist/runtime/annotation/engine/store.js +3 -0
- package/dist/runtime/annotation/engine/tools/arrow.js +0 -1
- package/dist/runtime/annotation/engine/tools/circle.js +0 -2
- package/dist/runtime/annotation/engine/tools/free-highlight.js +0 -2
- package/dist/runtime/annotation/engine/tools/freehand.js +0 -2
- package/dist/runtime/annotation/engine/tools/rectangle.js +0 -2
- package/dist/runtime/annotation/engine/types.d.ts +5 -0
- package/dist/runtime/annotation/font-style.d.ts +15 -0
- package/dist/runtime/annotation/font-style.js +19 -0
- package/dist/runtime/annotation/parsers/extractCheckboxStyles.d.ts +8 -0
- package/dist/runtime/annotation/parsers/extractCheckboxStyles.js +24 -1
- package/dist/runtime/annotation/pdf-export/annotation-flags.d.ts +8 -0
- package/dist/runtime/annotation/pdf-export/annotation-flags.js +5 -0
- package/dist/runtime/annotation/pdf-export/export-form-fields.js +3 -1
- package/dist/runtime/annotation/pdf-export/export.js +13 -1
- package/dist/runtime/annotation/pdf-export/parse_circle.js +3 -0
- package/dist/runtime/annotation/pdf-export/parse_freetext.js +31 -19
- package/dist/runtime/annotation/pdf-export/parse_highlight.js +3 -0
- package/dist/runtime/annotation/pdf-export/parse_ink.js +5 -1
- package/dist/runtime/annotation/pdf-export/parse_line.js +5 -1
- package/dist/runtime/annotation/pdf-export/parse_polyline.js +5 -1
- package/dist/runtime/annotation/pdf-export/parse_square.js +3 -0
- package/dist/runtime/annotation/pdf-export/parse_stamp.js +4 -2
- package/dist/runtime/annotation/pdf-export/parse_strikeout.js +3 -0
- package/dist/runtime/annotation/pdf-export/parse_text.js +3 -0
- package/dist/runtime/annotation/pdf-export/parse_underline.js +3 -0
- package/dist/runtime/annotation/pdf-export/unicode-font.d.ts +5 -0
- package/dist/runtime/annotation/pdf-export/unicode-font.js +31 -0
- package/dist/runtime/annotation/pdf-export/winansi.d.ts +17 -0
- package/dist/runtime/annotation/pdf-export/winansi.js +77 -0
- package/dist/runtime/annotation/pdf-import/decode_circle.js +0 -1
- package/dist/runtime/annotation/pdf-import/decode_ink.js +0 -1
- package/dist/runtime/annotation/pdf-import/decode_line.js +0 -1
- package/dist/runtime/annotation/pdf-import/decode_square.js +0 -1
- package/dist/runtime/annotation/pdf-import/extract_stamp_appearance.d.ts +12 -0
- package/dist/runtime/annotation/pdf-import/extract_stamp_appearance.js +17 -4
- package/dist/runtime/annotation/pdf-import/types.d.ts +2 -0
- package/dist/runtime/assets/fonts/LICENSE-LiberationSans +102 -0
- package/dist/runtime/assets/fonts/LiberationSans-Regular.ttf +0 -0
- package/dist/runtime/assets/kviewer.css +1 -1
- package/dist/runtime/components/AnnotationToolbar.vue +6 -4
- package/dist/runtime/components/PdfPage.d.vue.ts +16 -0
- package/dist/runtime/components/PdfPage.vue +170 -9
- package/dist/runtime/components/PdfPage.vue.d.ts +16 -0
- package/dist/runtime/components/PerfHud.d.vue.ts +3 -0
- package/dist/runtime/components/PerfHud.vue +97 -0
- package/dist/runtime/components/PerfHud.vue.d.ts +3 -0
- package/dist/runtime/components/ToolButton.vue +4 -1
- package/dist/runtime/components/Viewer.d.vue.ts +37 -2
- package/dist/runtime/components/Viewer.vue +208 -13
- package/dist/runtime/components/Viewer.vue.d.ts +37 -2
- package/dist/runtime/components/ViewerBar.d.vue.ts +21 -1
- package/dist/runtime/components/ViewerBar.vue +183 -101
- package/dist/runtime/components/ViewerBar.vue.d.ts +21 -1
- package/dist/runtime/components/ViewerTabs.d.vue.ts +10 -2
- package/dist/runtime/components/ViewerTabs.vue +13 -2
- package/dist/runtime/components/ViewerTabs.vue.d.ts +10 -2
- package/dist/runtime/components/form-fields/FormDropdown.vue +50 -19
- package/dist/runtime/components/form-fields/FormSignatureField.vue +4 -2
- package/dist/runtime/components/form-fields/FormTextField.vue +7 -18
- package/dist/runtime/components/modals/FreeTextModal.vue +6 -4
- package/dist/runtime/components/modals/SignatureDrawModal.vue +6 -4
- package/dist/runtime/components/panels/PlacedFieldSidebar.vue +67 -50
- package/dist/runtime/components/panels/SignaturePicker.vue +6 -4
- package/dist/runtime/components/panels/StampPicker.vue +3 -1
- package/dist/runtime/components/tools/AnnotationToolButton.d.vue.ts +10 -0
- package/dist/runtime/components/tools/AnnotationToolButton.vue +33 -0
- package/dist/runtime/components/tools/AnnotationToolButton.vue.d.ts +10 -0
- package/dist/runtime/components/tools/MarqueeTool.vue +3 -1
- package/dist/runtime/components/tools/PageSettings.vue +13 -11
- package/dist/runtime/components/tools/SearchTool.vue +12 -10
- package/dist/runtime/components/tools/ToolProperties.vue +7 -5
- package/dist/runtime/components/tools/ToolbarMenu.d.vue.ts +9 -0
- package/dist/runtime/components/tools/ToolbarMenu.vue +79 -0
- package/dist/runtime/components/tools/ToolbarMenu.vue.d.ts +9 -0
- package/dist/runtime/components/tools/ZoomControls.vue +17 -9
- package/dist/runtime/composables/canvas-limits.d.ts +22 -0
- package/dist/runtime/composables/canvas-limits.js +10 -0
- package/dist/runtime/composables/useFormFields.d.ts +6 -0
- package/dist/runtime/composables/useFormFields.js +28 -0
- package/dist/runtime/composables/useInertiaPanzoom.d.ts +10 -0
- package/dist/runtime/composables/useInertiaPanzoom.js +57 -10
- package/dist/runtime/composables/useKviewerI18n.d.ts +18 -0
- package/dist/runtime/composables/useKviewerI18n.js +29 -0
- package/dist/runtime/composables/usePageVirtualization.d.ts +11 -0
- package/dist/runtime/composables/usePageVirtualization.js +40 -0
- package/dist/runtime/composables/useScriptingManager.d.ts +8 -4
- package/dist/runtime/composables/useScriptingManager.js +2 -0
- package/dist/runtime/composables/useViewerState.d.ts +4 -0
- package/dist/runtime/composables/useViewerState.js +8 -0
- package/dist/runtime/embed/bridge-client.d.ts +9 -1
- package/dist/runtime/embed/bridge-client.js +13 -0
- package/dist/runtime/embed/bridge-host.d.ts +3 -0
- package/dist/runtime/embed/bridge-host.js +30 -8
- package/dist/runtime/embed/protocol.d.ts +21 -0
- package/dist/runtime/i18n/messages.d.ts +252 -0
- package/dist/runtime/i18n/messages.js +249 -0
- package/dist/runtime/public-types.d.ts +2 -0
- package/dist/runtime/toolbar-tools.d.ts +54 -0
- package/dist/runtime/toolbar-tools.js +79 -0
- package/dist/types.d.mts +1 -1
- package/package.json +14 -31
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
:variant="search.isOpen.value ? 'soft' : 'ghost'"
|
|
6
6
|
color="neutral"
|
|
7
7
|
size="xs"
|
|
8
|
-
title="
|
|
8
|
+
:title="t('search')"
|
|
9
9
|
data-testid="search-toggle"
|
|
10
10
|
@click="toggleOpen"
|
|
11
11
|
/>
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
<UInput
|
|
18
18
|
ref="queryInputRef"
|
|
19
19
|
:model-value="search.query.value"
|
|
20
|
-
placeholder="
|
|
20
|
+
:placeholder="t('searchPlaceholder')"
|
|
21
21
|
variant="outline"
|
|
22
22
|
color="neutral"
|
|
23
23
|
size="xs"
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
color="neutral"
|
|
38
38
|
size="xs"
|
|
39
39
|
:disabled="!hasMatches"
|
|
40
|
-
title="
|
|
40
|
+
:title="t('previousMatch')"
|
|
41
41
|
@click="search.findPrevious"
|
|
42
42
|
/>
|
|
43
43
|
<UButton
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
color="neutral"
|
|
47
47
|
size="xs"
|
|
48
48
|
:disabled="!hasMatches"
|
|
49
|
-
title="
|
|
49
|
+
:title="t('nextMatch')"
|
|
50
50
|
@click="search.findNext"
|
|
51
51
|
/>
|
|
52
52
|
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
:variant="search.matchCase.value ? 'soft' : 'ghost'"
|
|
55
55
|
color="neutral"
|
|
56
56
|
size="xs"
|
|
57
|
-
title="
|
|
57
|
+
:title="t('matchCase')"
|
|
58
58
|
@click="search.toggleMatchCase"
|
|
59
59
|
>
|
|
60
60
|
Aa
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
:variant="search.wholeWord.value ? 'soft' : 'ghost'"
|
|
64
64
|
color="neutral"
|
|
65
65
|
size="xs"
|
|
66
|
-
title="
|
|
66
|
+
:title="t('wholeWord')"
|
|
67
67
|
@click="search.toggleWholeWord"
|
|
68
68
|
>
|
|
69
69
|
W
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
:variant="search.highlightAll.value ? 'soft' : 'ghost'"
|
|
73
73
|
color="neutral"
|
|
74
74
|
size="xs"
|
|
75
|
-
title="
|
|
75
|
+
:title="t('highlightAll')"
|
|
76
76
|
@click="search.toggleHighlightAll"
|
|
77
77
|
>
|
|
78
78
|
All
|
|
@@ -83,7 +83,7 @@
|
|
|
83
83
|
variant="ghost"
|
|
84
84
|
color="neutral"
|
|
85
85
|
size="xs"
|
|
86
|
-
title="
|
|
86
|
+
:title="t('closeSearch')"
|
|
87
87
|
@click="search.close"
|
|
88
88
|
/>
|
|
89
89
|
</div>
|
|
@@ -93,12 +93,14 @@
|
|
|
93
93
|
<script setup>
|
|
94
94
|
import { computed, nextTick, onMounted, ref, watch } from "vue";
|
|
95
95
|
import { useViewerSearch } from "../../composables/useViewerSearch";
|
|
96
|
+
import { useKviewerI18n } from "../../composables/useKviewerI18n";
|
|
96
97
|
const search = useViewerSearch();
|
|
98
|
+
const { t } = useKviewerI18n();
|
|
97
99
|
const queryInputRef = ref(null);
|
|
98
100
|
const hasMatches = computed(() => search.totalMatches.value > 0);
|
|
99
101
|
const statusLabel = computed(() => {
|
|
100
|
-
if (!search.query.value) return "
|
|
101
|
-
if (search.notFound.value) return "
|
|
102
|
+
if (!search.query.value) return t("typeToSearch");
|
|
103
|
+
if (search.notFound.value) return t("noResults");
|
|
102
104
|
return `${search.currentMatch.value}/${search.totalMatches.value}`;
|
|
103
105
|
});
|
|
104
106
|
function resolveInputElement() {
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
<UPopover v-model:open="colorPopoverOpen">
|
|
41
41
|
<button
|
|
42
42
|
class="flex items-center justify-center w-7 h-7 rounded-md hover:bg-default cursor-pointer"
|
|
43
|
-
title="
|
|
43
|
+
:title="t('color')"
|
|
44
44
|
>
|
|
45
45
|
<span
|
|
46
46
|
class="w-4 h-4 rounded-full border border-white/30"
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
<UPopover v-if="activeType === AnnotationType.FREEHAND">
|
|
68
68
|
<button
|
|
69
69
|
class="flex items-center justify-center h-7 px-1.5 gap-0.5 rounded-md hover:bg-default cursor-pointer"
|
|
70
|
-
title="
|
|
70
|
+
:title="t('strokeWidth')"
|
|
71
71
|
>
|
|
72
72
|
<UIcon name="i-lucide-pencil" class="text-sm text-muted" />
|
|
73
73
|
<span class="text-[10px] text-muted tabular-nums">{{ freehandStrokeWidth }}</span>
|
|
@@ -77,7 +77,7 @@
|
|
|
77
77
|
<button
|
|
78
78
|
class="flex items-center justify-center w-8 h-8 rounded-md hover:bg-default cursor-pointer disabled:opacity-40 disabled:cursor-not-allowed"
|
|
79
79
|
:disabled="freehandStrokeWidth <= 1"
|
|
80
|
-
title="
|
|
80
|
+
:title="t('decreaseStrokeWidth')"
|
|
81
81
|
@click="decrementStrokeWidth"
|
|
82
82
|
>
|
|
83
83
|
<UIcon name="i-lucide-minus" class="text-sm" />
|
|
@@ -88,7 +88,7 @@
|
|
|
88
88
|
<button
|
|
89
89
|
class="flex items-center justify-center w-8 h-8 rounded-md hover:bg-default cursor-pointer disabled:opacity-40 disabled:cursor-not-allowed"
|
|
90
90
|
:disabled="freehandStrokeWidth >= 24"
|
|
91
|
-
title="
|
|
91
|
+
:title="t('increaseStrokeWidth')"
|
|
92
92
|
@click="incrementStrokeWidth"
|
|
93
93
|
>
|
|
94
94
|
<UIcon name="i-lucide-plus" class="text-sm" />
|
|
@@ -102,7 +102,7 @@
|
|
|
102
102
|
<template v-else>
|
|
103
103
|
<div class="flex items-center gap-1.5 text-xs text-muted">
|
|
104
104
|
<UIcon name="i-lucide-sliders-horizontal" class="text-sm" />
|
|
105
|
-
<span>
|
|
105
|
+
<span>{{ t("noProperties") }}</span>
|
|
106
106
|
</div>
|
|
107
107
|
</template>
|
|
108
108
|
</div>
|
|
@@ -113,8 +113,10 @@ import { computed, ref } from "vue";
|
|
|
113
113
|
import { defaultOptions, annotationDefinitions } from "../../annotation/engine/config";
|
|
114
114
|
import { AnnotationType } from "../../annotation/engine/types";
|
|
115
115
|
import { useViewerState } from "../../composables/useViewerState";
|
|
116
|
+
import { useKviewerI18n } from "../../composables/useKviewerI18n";
|
|
116
117
|
const emit = defineEmits(["styleUpdated"]);
|
|
117
118
|
const state = useViewerState();
|
|
119
|
+
const { t } = useKviewerI18n();
|
|
118
120
|
const colors = defaultOptions.colors;
|
|
119
121
|
const styleVersion = ref(0);
|
|
120
122
|
const colorPopoverOpen = ref(false);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ViewerMenuItem } from '../../menu-items.js';
|
|
2
|
+
type __VLS_Props = {
|
|
3
|
+
/** Extra items appended to the burger menu after the built-ins.
|
|
4
|
+
* See {@link ViewerMenuItem}. Pure forwarding from `<KViewer>`. */
|
|
5
|
+
menuItems?: ViewerMenuItem[];
|
|
6
|
+
};
|
|
7
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
8
|
+
declare const _default: typeof __VLS_export;
|
|
9
|
+
export default _default;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<UPopover>
|
|
3
|
+
<UButton icon="i-lucide-menu" variant="ghost" color="neutral" size="xs" data-testid="viewer-menu" />
|
|
4
|
+
<template #content>
|
|
5
|
+
<div class="p-1 flex flex-col gap-0.5">
|
|
6
|
+
<UButton
|
|
7
|
+
icon="i-lucide-download"
|
|
8
|
+
:label="t('download')"
|
|
9
|
+
variant="ghost"
|
|
10
|
+
color="neutral"
|
|
11
|
+
size="xs"
|
|
12
|
+
class="w-full justify-start"
|
|
13
|
+
data-testid="viewer-download"
|
|
14
|
+
@click="onDownload"
|
|
15
|
+
/>
|
|
16
|
+
<UButton
|
|
17
|
+
:icon="state.shapeDetection.value ? 'i-lucide-square-check-big' : 'i-lucide-square-dashed'"
|
|
18
|
+
:label="state.shapeDetection.value ? t('disableFormFieldDetection') : t('detectFormFields')"
|
|
19
|
+
:variant="state.shapeDetection.value ? 'soft' : 'ghost'"
|
|
20
|
+
:color="state.shapeDetection.value ? 'primary' : 'neutral'"
|
|
21
|
+
size="xs"
|
|
22
|
+
class="w-full justify-start"
|
|
23
|
+
data-testid="viewer-toggle-shape-detection"
|
|
24
|
+
@click="state.shapeDetection.value = !state.shapeDetection.value"
|
|
25
|
+
/>
|
|
26
|
+
<!-- Host-supplied menu items appended after the built-ins.
|
|
27
|
+
A small separator visually marks the boundary; we only
|
|
28
|
+
render it when there is at least one custom item. -->
|
|
29
|
+
<template v-if="props.menuItems && props.menuItems.length > 0">
|
|
30
|
+
<div class="my-1 border-t border-default" />
|
|
31
|
+
<template v-for="item in props.menuItems" :key="item.key">
|
|
32
|
+
<div v-if="item.type === 'separator'" class="my-1 border-t border-default" />
|
|
33
|
+
<UButton
|
|
34
|
+
v-else-if="item.type === 'button'"
|
|
35
|
+
:icon="item.icon"
|
|
36
|
+
:label="item.label"
|
|
37
|
+
:disabled="item.disabled"
|
|
38
|
+
variant="ghost"
|
|
39
|
+
color="neutral"
|
|
40
|
+
size="xs"
|
|
41
|
+
class="w-full justify-start"
|
|
42
|
+
:data-testid="`viewer-menu-${item.key}`"
|
|
43
|
+
@click="item.onSelect()"
|
|
44
|
+
/>
|
|
45
|
+
<UButton
|
|
46
|
+
v-else-if="item.type === 'checkbox'"
|
|
47
|
+
:icon="item.icon ?? (item.checked ? 'i-lucide-check-square' : 'i-lucide-square')"
|
|
48
|
+
:label="item.label"
|
|
49
|
+
:variant="item.checked ? 'soft' : 'ghost'"
|
|
50
|
+
:color="item.checked ? 'primary' : 'neutral'"
|
|
51
|
+
:disabled="item.disabled"
|
|
52
|
+
size="xs"
|
|
53
|
+
class="w-full justify-start"
|
|
54
|
+
:data-testid="`viewer-menu-${item.key}`"
|
|
55
|
+
@click="item.onUpdate(!item.checked)"
|
|
56
|
+
/>
|
|
57
|
+
</template>
|
|
58
|
+
</template>
|
|
59
|
+
</div>
|
|
60
|
+
</template>
|
|
61
|
+
</UPopover>
|
|
62
|
+
</template>
|
|
63
|
+
|
|
64
|
+
<script setup>
|
|
65
|
+
import { useViewerState } from "../../composables/useViewerState";
|
|
66
|
+
import { useKviewerI18n } from "../../composables/useKviewerI18n";
|
|
67
|
+
const props = defineProps({
|
|
68
|
+
menuItems: { type: Array, required: false }
|
|
69
|
+
});
|
|
70
|
+
const state = useViewerState();
|
|
71
|
+
const { t } = useKviewerI18n();
|
|
72
|
+
async function onDownload() {
|
|
73
|
+
try {
|
|
74
|
+
await state.downloadPdf();
|
|
75
|
+
} catch (error) {
|
|
76
|
+
console.error("Failed to export PDF from menu action", error);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
</script>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ViewerMenuItem } from '../../menu-items.js';
|
|
2
|
+
type __VLS_Props = {
|
|
3
|
+
/** Extra items appended to the burger menu after the built-ins.
|
|
4
|
+
* See {@link ViewerMenuItem}. Pure forwarding from `<KViewer>`. */
|
|
5
|
+
menuItems?: ViewerMenuItem[];
|
|
6
|
+
};
|
|
7
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
8
|
+
declare const _default: typeof __VLS_export;
|
|
9
|
+
export default _default;
|
|
@@ -32,19 +32,27 @@
|
|
|
32
32
|
<script setup>
|
|
33
33
|
import { computed } from "vue";
|
|
34
34
|
import { useViewerState } from "../../composables/useViewerState";
|
|
35
|
+
import { useKviewerI18n } from "../../composables/useKviewerI18n";
|
|
35
36
|
const state = useViewerState();
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
{ label: "
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
37
|
+
const { t } = useKviewerI18n();
|
|
38
|
+
const fitModeItems = computed(() => [
|
|
39
|
+
{ label: t("fitWidth"), value: "fit-width" },
|
|
40
|
+
{ label: t("fitPage"), value: "fit-page" }
|
|
41
|
+
]);
|
|
42
|
+
const scaleItems = computed(() => {
|
|
43
|
+
const options = [...state.scaleOptions];
|
|
44
|
+
const current = state.scale.value;
|
|
45
|
+
if (state.fitMode.value === "custom" && !options.includes(current)) {
|
|
46
|
+
options.push(current);
|
|
47
|
+
options.sort((a, b) => a - b);
|
|
48
|
+
}
|
|
49
|
+
return options.map((s) => ({
|
|
42
50
|
label: `${Math.round(s * 100)}%`,
|
|
43
51
|
value: `scale:${s}`
|
|
44
|
-
}))
|
|
45
|
-
);
|
|
52
|
+
}));
|
|
53
|
+
});
|
|
46
54
|
const zoomItems = computed(() => [
|
|
47
|
-
...fitModeItems,
|
|
55
|
+
...fitModeItems.value,
|
|
48
56
|
{ label: "---", value: "_separator", disabled: true },
|
|
49
57
|
...scaleItems.value
|
|
50
58
|
]);
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canvas backing-store limits.
|
|
3
|
+
*
|
|
4
|
+
* iOS Safari rejects canvases above ~16.7M pixels — the allocation fails
|
|
5
|
+
* SILENTLY and the page renders blank. Large PDF pages on iPad (DPR 2,
|
|
6
|
+
* supersampling) cross that limit at moderate zoom. Other engines allow
|
|
7
|
+
* far larger canvases; 64M keeps memory bounded without visibly capping
|
|
8
|
+
* desktop zoom. pdfjs's own viewer applies the same strategy via its
|
|
9
|
+
* `maxCanvasPixels` option (default 2^24 ≈ 16.7M).
|
|
10
|
+
*/
|
|
11
|
+
export declare const IOS_MAX_CANVAS_PIXELS = 16000000;
|
|
12
|
+
export declare const DEFAULT_MAX_CANVAS_PIXELS = 64000000;
|
|
13
|
+
/**
|
|
14
|
+
* Clamp a desired render scale so the resulting backing store stays under
|
|
15
|
+
* `maxPixels`. The canvas's CSS size is set independently, so clamping
|
|
16
|
+
* only reduces sharpness at extreme zoom instead of blanking the page.
|
|
17
|
+
*
|
|
18
|
+
* @param desiredScale page-to-canvas scale (zoom × quality × pixel ratio)
|
|
19
|
+
* @param baseWidth page width at scale 1
|
|
20
|
+
* @param baseHeight page height at scale 1
|
|
21
|
+
*/
|
|
22
|
+
export declare function clampRenderScale(desiredScale: number, baseWidth: number, baseHeight: number, maxPixels: number): number;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export const IOS_MAX_CANVAS_PIXELS = 16e6;
|
|
2
|
+
export const DEFAULT_MAX_CANVAS_PIXELS = 64e6;
|
|
3
|
+
export function clampRenderScale(desiredScale, baseWidth, baseHeight, maxPixels) {
|
|
4
|
+
const basePixels = baseWidth * baseHeight;
|
|
5
|
+
if (basePixels <= 0) return desiredScale;
|
|
6
|
+
if (basePixels * desiredScale * desiredScale <= maxPixels) {
|
|
7
|
+
return desiredScale;
|
|
8
|
+
}
|
|
9
|
+
return Math.sqrt(maxPixels / basePixels);
|
|
10
|
+
}
|
|
@@ -62,6 +62,12 @@ export interface FormFieldsState {
|
|
|
62
62
|
* PDF) to existing parsed button defs and store the map for any defs
|
|
63
63
|
* parsed afterwards. Keyed by button fieldName. */
|
|
64
64
|
applyButtonCaptions: (captions: Map<string, string>) => void;
|
|
65
|
+
/** Mark parsed signature defs whose source-PDF field is already
|
|
66
|
+
* digitally signed (`/V` holds a `/Sig` dict — extracted via the
|
|
67
|
+
* pdf-lib side-channel; pdf.js nulls a signature widget's fieldValue
|
|
68
|
+
* so the signed state is invisible in its annotation objects). Keyed
|
|
69
|
+
* by fully qualified field name. */
|
|
70
|
+
applySignedSignatures: (names: Set<string>) => void;
|
|
65
71
|
/** Whether this field is locked by another (signed) signature field's
|
|
66
72
|
* lock rule. Reactive to both definitions and values, so a UI computed
|
|
67
73
|
* that calls this re-runs when any signature is signed or its lock
|
|
@@ -37,6 +37,7 @@ export function provideFormFields() {
|
|
|
37
37
|
}
|
|
38
38
|
let checkboxStyleMap = null;
|
|
39
39
|
let buttonCaptionMap = null;
|
|
40
|
+
let signedSignatureNames = null;
|
|
40
41
|
function applyCheckboxStyles(styles) {
|
|
41
42
|
checkboxStyleMap = styles;
|
|
42
43
|
let changed = false;
|
|
@@ -77,6 +78,24 @@ export function provideFormFields() {
|
|
|
77
78
|
}
|
|
78
79
|
if (changed) triggerRef(fieldDefinitions);
|
|
79
80
|
}
|
|
81
|
+
function applySignedSignatures(names) {
|
|
82
|
+
signedSignatureNames = names;
|
|
83
|
+
let changed = false;
|
|
84
|
+
for (const [page, defs] of fieldDefinitions.value.entries()) {
|
|
85
|
+
let pageChanged = false;
|
|
86
|
+
const next = defs.map((d) => {
|
|
87
|
+
if (d.fieldType !== "signature" || d.origin !== "parsed") return d;
|
|
88
|
+
if (d.signedInSource || !names.has(d.fieldName)) return d;
|
|
89
|
+
pageChanged = true;
|
|
90
|
+
return { ...d, signedInSource: true };
|
|
91
|
+
});
|
|
92
|
+
if (pageChanged) {
|
|
93
|
+
fieldDefinitions.value.set(page, next);
|
|
94
|
+
changed = true;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (changed) triggerRef(fieldDefinitions);
|
|
98
|
+
}
|
|
80
99
|
function parsePageFields(pageNumber, rawAnnotations) {
|
|
81
100
|
if (fieldDefinitions.value.has(pageNumber)) return;
|
|
82
101
|
const defs = parseFormFields(rawAnnotations, pageNumber);
|
|
@@ -99,6 +118,13 @@ export function provideFormFields() {
|
|
|
99
118
|
if (caption) def.buttonLabel = caption;
|
|
100
119
|
}
|
|
101
120
|
}
|
|
121
|
+
if (signedSignatureNames) {
|
|
122
|
+
for (const def of defs) {
|
|
123
|
+
if (def.fieldType === "signature" && signedSignatureNames.has(def.fieldName)) {
|
|
124
|
+
def.signedInSource = true;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
102
128
|
fieldDefinitions.value.set(pageNumber, defs);
|
|
103
129
|
triggerRef(fieldDefinitions);
|
|
104
130
|
for (const def of defs) {
|
|
@@ -464,6 +490,7 @@ export function provideFormFields() {
|
|
|
464
490
|
selectedPlacedFieldId.value = null;
|
|
465
491
|
checkboxStyleMap = null;
|
|
466
492
|
buttonCaptionMap = null;
|
|
493
|
+
signedSignatureNames = null;
|
|
467
494
|
valueWriteSink = null;
|
|
468
495
|
triggerRef(fieldDefinitions);
|
|
469
496
|
triggerRef(fieldValues);
|
|
@@ -489,6 +516,7 @@ export function provideFormFields() {
|
|
|
489
516
|
selectPlacedField,
|
|
490
517
|
applyCheckboxStyles,
|
|
491
518
|
applyButtonCaptions,
|
|
519
|
+
applySignedSignatures,
|
|
492
520
|
isFieldLocked,
|
|
493
521
|
resetValues,
|
|
494
522
|
reset,
|
|
@@ -36,5 +36,15 @@ export interface InertiaPanzoomHandle {
|
|
|
36
36
|
setGesturesEnabled: (enabled: boolean) => void;
|
|
37
37
|
/** Subscribe to transform updates. Returns an unsubscribe function. */
|
|
38
38
|
onChange: (cb: (t: Transform) => void) => () => void;
|
|
39
|
+
/** Subscribe to the end of a zoom gesture (pinch lifted / ctrl+wheel idle).
|
|
40
|
+
* Fires with the current visual scale so the host can commit it into the
|
|
41
|
+
* PDF render scale. Returns an unsubscribe function. */
|
|
42
|
+
onZoomEnd: (cb: (scale: number) => void) => () => void;
|
|
43
|
+
/** Replace min/max visual scale at runtime (e.g. to cap the *effective*
|
|
44
|
+
* zoom when the committed render scale changes). */
|
|
45
|
+
setScaleLimits: (min: number, max: number) => void;
|
|
46
|
+
/** Set the transform directly (translate clamped to bounds). In-flight
|
|
47
|
+
* gestures are rebased so they continue from the new transform. */
|
|
48
|
+
setTransform: (t: Partial<Transform>) => void;
|
|
39
49
|
}
|
|
40
50
|
export declare function useInertiaPanzoom(options?: InertiaPanzoomOptions): InertiaPanzoomHandle;
|
|
@@ -6,11 +6,13 @@ const BOUNCE_VELOCITY_DECAY = 0.04;
|
|
|
6
6
|
const MIN_VELOCITY = 0.01;
|
|
7
7
|
export function useInertiaPanzoom(options = {}) {
|
|
8
8
|
const {
|
|
9
|
-
minScale = 0.5,
|
|
10
|
-
maxScale = 4,
|
|
11
9
|
axisLockThreshold = 10,
|
|
12
10
|
axisLockRatio = 2
|
|
13
11
|
} = options;
|
|
12
|
+
let minScale = options.minScale ?? 0.5;
|
|
13
|
+
let maxScale = options.maxScale ?? 4;
|
|
14
|
+
const subscribers = /* @__PURE__ */ new Set();
|
|
15
|
+
const zoomEndSubscribers = /* @__PURE__ */ new Set();
|
|
14
16
|
let controller = null;
|
|
15
17
|
function attach(target) {
|
|
16
18
|
detach();
|
|
@@ -30,7 +32,11 @@ export function useInertiaPanzoom(options = {}) {
|
|
|
30
32
|
let vy = 0;
|
|
31
33
|
let momentumRaf = 0;
|
|
32
34
|
let lastStepTime = 0;
|
|
33
|
-
|
|
35
|
+
let pinchZoomed = false;
|
|
36
|
+
let wheelZoomEndTimer = 0;
|
|
37
|
+
function fireZoomEnd() {
|
|
38
|
+
zoomEndSubscribers.forEach((cb) => cb(scale));
|
|
39
|
+
}
|
|
34
40
|
target.style.transformOrigin = "0 0";
|
|
35
41
|
target.style.touchAction = "none";
|
|
36
42
|
parent.style.touchAction = "none";
|
|
@@ -320,6 +326,7 @@ export function useInertiaPanzoom(options = {}) {
|
|
|
320
326
|
const focalY = (pinchStart.midY - rect.top - pinchStart.ty) / pinchStart.s;
|
|
321
327
|
tx = midX - rect.left - newScale * focalX;
|
|
322
328
|
ty = midY - rect.top - newScale * focalY;
|
|
329
|
+
if (newScale !== scale) pinchZoomed = true;
|
|
323
330
|
scale = newScale;
|
|
324
331
|
apply();
|
|
325
332
|
} else if (pointers.size === 1 && panStart) {
|
|
@@ -352,7 +359,7 @@ export function useInertiaPanzoom(options = {}) {
|
|
|
352
359
|
}
|
|
353
360
|
}
|
|
354
361
|
function onUp(e) {
|
|
355
|
-
pointers.delete(e.pointerId);
|
|
362
|
+
if (!pointers.delete(e.pointerId)) return;
|
|
356
363
|
if (pointers.size < 2) pinchStart = null;
|
|
357
364
|
if (pointers.size === 0) {
|
|
358
365
|
const hadPan = panStart !== null;
|
|
@@ -365,6 +372,10 @@ export function useInertiaPanzoom(options = {}) {
|
|
|
365
372
|
}
|
|
366
373
|
if (hadPan && lockedAxis === "y") vx = 0;
|
|
367
374
|
startMomentum();
|
|
375
|
+
if (pinchZoomed) {
|
|
376
|
+
pinchZoomed = false;
|
|
377
|
+
fireZoomEnd();
|
|
378
|
+
}
|
|
368
379
|
}
|
|
369
380
|
}
|
|
370
381
|
function onWheelZoom(e) {
|
|
@@ -382,6 +393,33 @@ export function useInertiaPanzoom(options = {}) {
|
|
|
382
393
|
ty = my - newScale * focalY;
|
|
383
394
|
scale = newScale;
|
|
384
395
|
apply();
|
|
396
|
+
clearTimeout(wheelZoomEndTimer);
|
|
397
|
+
wheelZoomEndTimer = window.setTimeout(fireZoomEnd, 200);
|
|
398
|
+
}
|
|
399
|
+
function setTransform(t) {
|
|
400
|
+
stopMomentum();
|
|
401
|
+
if (t.scale !== void 0) scale = Math.max(minScale, Math.min(maxScale, t.scale));
|
|
402
|
+
if (t.tx !== void 0) tx = t.tx;
|
|
403
|
+
if (t.ty !== void 0) ty = t.ty;
|
|
404
|
+
const b = getBounds();
|
|
405
|
+
tx = Math.max(b.minX, Math.min(b.maxX, tx));
|
|
406
|
+
ty = Math.max(b.minY, Math.min(b.maxY, ty));
|
|
407
|
+
if (pinchStart && pointers.size >= 2) {
|
|
408
|
+
const pts = [...pointers.values()];
|
|
409
|
+
pinchStart = {
|
|
410
|
+
dist: Math.hypot(pts[0].x - pts[1].x, pts[0].y - pts[1].y),
|
|
411
|
+
midX: (pts[0].x + pts[1].x) / 2,
|
|
412
|
+
midY: (pts[0].y + pts[1].y) / 2,
|
|
413
|
+
s: scale,
|
|
414
|
+
tx,
|
|
415
|
+
ty
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
if (panStart && pointers.size === 1) {
|
|
419
|
+
const p = [...pointers.values()][0];
|
|
420
|
+
panStart = { x: p.x, y: p.y, tx, ty, axisLock: panStart.axisLock };
|
|
421
|
+
}
|
|
422
|
+
apply();
|
|
385
423
|
}
|
|
386
424
|
function scrollBy(dx, dy) {
|
|
387
425
|
stopMomentum();
|
|
@@ -401,6 +439,7 @@ export function useInertiaPanzoom(options = {}) {
|
|
|
401
439
|
cleanup: () => {
|
|
402
440
|
stopMomentum();
|
|
403
441
|
clearTimeout(hideTimer);
|
|
442
|
+
clearTimeout(wheelZoomEndTimer);
|
|
404
443
|
parent.removeEventListener("pointerdown", onDown);
|
|
405
444
|
parent.removeEventListener("pointermove", onMove);
|
|
406
445
|
parent.removeEventListener("pointerup", onUp);
|
|
@@ -449,10 +488,7 @@ export function useInertiaPanzoom(options = {}) {
|
|
|
449
488
|
setWillChangeActive(false);
|
|
450
489
|
}
|
|
451
490
|
},
|
|
452
|
-
|
|
453
|
-
subscribers.add(cb);
|
|
454
|
-
return () => subscribers.delete(cb);
|
|
455
|
-
}
|
|
491
|
+
setTransform
|
|
456
492
|
};
|
|
457
493
|
}
|
|
458
494
|
function detach() {
|
|
@@ -470,7 +506,18 @@ export function useInertiaPanzoom(options = {}) {
|
|
|
470
506
|
scrollToElement: (elem, opts) => controller?.scrollToElement(elem, opts),
|
|
471
507
|
scrollBy: (dx, dy) => controller?.scrollBy(dx, dy),
|
|
472
508
|
setGesturesEnabled: (enabled) => controller?.setGesturesEnabled(enabled),
|
|
473
|
-
|
|
474
|
-
|
|
509
|
+
setTransform: (t) => controller?.setTransform(t),
|
|
510
|
+
setScaleLimits: (min, max) => {
|
|
511
|
+
minScale = min;
|
|
512
|
+
maxScale = max;
|
|
513
|
+
},
|
|
514
|
+
onChange: (cb) => {
|
|
515
|
+
subscribers.add(cb);
|
|
516
|
+
return () => subscribers.delete(cb);
|
|
517
|
+
},
|
|
518
|
+
onZoomEnd: (cb) => {
|
|
519
|
+
zoomEndSubscribers.add(cb);
|
|
520
|
+
return () => zoomEndSubscribers.delete(cb);
|
|
521
|
+
}
|
|
475
522
|
};
|
|
476
523
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type ComputedRef, type InjectionKey } from 'vue';
|
|
2
|
+
import { type KviewerLocale, type MessageKey } from '../i18n/messages.js';
|
|
3
|
+
export type KviewerMessageOverrides = Partial<Record<MessageKey, string>>;
|
|
4
|
+
export declare const kviewerLocaleKey: InjectionKey<ComputedRef<KviewerLocale>>;
|
|
5
|
+
export declare const kviewerMessagesKey: InjectionKey<ComputedRef<KviewerMessageOverrides>>;
|
|
6
|
+
/** Called once by the Viewer root to make locale + overrides available. */
|
|
7
|
+
export declare function provideKviewerI18n(locale: ComputedRef<KviewerLocale>, overrides: ComputedRef<KviewerMessageOverrides>): void;
|
|
8
|
+
/**
|
|
9
|
+
* Translate built-in viewer strings.
|
|
10
|
+
*
|
|
11
|
+
* Resolution order: host override (`messages` prop) → active locale →
|
|
12
|
+
* English. `{param}` placeholders are substituted from `params`.
|
|
13
|
+
*/
|
|
14
|
+
export declare function useKviewerI18n(): {
|
|
15
|
+
t: (key: MessageKey, params?: Record<string, string | number>) => string;
|
|
16
|
+
toolLabel: (name: string) => string;
|
|
17
|
+
locale: ComputedRef<"en" | "de">;
|
|
18
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { computed, inject, provide } from "vue";
|
|
2
|
+
import { messages } from "../i18n/messages.js";
|
|
3
|
+
export const kviewerLocaleKey = Symbol("kviewer-locale");
|
|
4
|
+
export const kviewerMessagesKey = Symbol("kviewer-messages");
|
|
5
|
+
export function provideKviewerI18n(locale, overrides) {
|
|
6
|
+
provide(kviewerLocaleKey, locale);
|
|
7
|
+
provide(kviewerMessagesKey, overrides);
|
|
8
|
+
}
|
|
9
|
+
export function useKviewerI18n() {
|
|
10
|
+
const locale = inject(kviewerLocaleKey, computed(() => "en"));
|
|
11
|
+
const overrides = inject(
|
|
12
|
+
kviewerMessagesKey,
|
|
13
|
+
computed(() => ({}))
|
|
14
|
+
);
|
|
15
|
+
function t(key, params) {
|
|
16
|
+
let message = overrides.value[key] ?? messages[locale.value]?.[key] ?? messages.en[key] ?? key;
|
|
17
|
+
if (params) {
|
|
18
|
+
for (const [name, value] of Object.entries(params)) {
|
|
19
|
+
message = message.replace(`{${name}}`, String(value));
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return message;
|
|
23
|
+
}
|
|
24
|
+
function toolLabel(name) {
|
|
25
|
+
const key = `tool${name.charAt(0).toUpperCase()}${name.slice(1)}`;
|
|
26
|
+
return key in messages.en ? t(key) : name;
|
|
27
|
+
}
|
|
28
|
+
return { t, toolLabel, locale };
|
|
29
|
+
}
|
|
@@ -21,6 +21,17 @@ export interface PageVirtualization {
|
|
|
21
21
|
pageMetas: ShallowRef<PageMeta[]>;
|
|
22
22
|
currentPage: Ref<number>;
|
|
23
23
|
renderedPages: ComputedRef<Set<number>>;
|
|
24
|
+
/** Pages that have entered the viewport at least once since the document
|
|
25
|
+
* loaded (or since the last `resetViewedPages()`). Marked from scroll/pan
|
|
26
|
+
* geometry — not render state — so a fast fling that skips rendering the
|
|
27
|
+
* middle pages still counts them as viewed. */
|
|
28
|
+
viewedPages: ShallowRef<Set<number>>;
|
|
29
|
+
/** True once every page has been viewed. */
|
|
30
|
+
allPagesRead: ComputedRef<boolean>;
|
|
31
|
+
/** Mark a single page as viewed (used by single-page navigation). */
|
|
32
|
+
markPageViewed: (pageNumber: number) => void;
|
|
33
|
+
/** Clear the viewed-pages set, e.g. to start a fresh signing session. */
|
|
34
|
+
resetViewedPages: () => void;
|
|
24
35
|
isPageRendered: (pageNumber: number) => boolean;
|
|
25
36
|
observePage: (pageNumber: number, element: HTMLElement) => void;
|
|
26
37
|
unobservePage: (pageNumber: number) => void;
|