-
-
Notifications
You must be signed in to change notification settings - Fork 76
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #66 from dlnsk/master
Provide outline data to build TOC
- Loading branch information
Showing
8 changed files
with
244 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
<script> | ||
export default { | ||
name: 'ChaptersList', | ||
props: { | ||
items: Array, | ||
}, | ||
methods: { | ||
onChapterClick: function (e) { | ||
this.$emit('chapterClick', e) | ||
}, | ||
}, | ||
} | ||
</script> | ||
|
||
<template> | ||
<ol> | ||
<li v-for="item in items"> | ||
<a href="#" @click.prevent="$emit('chapterClick', item.destination)">{{ item.title }}</a> | ||
<div v-if="item.items.length"> | ||
<ChaptersList | ||
:items="item.items" | ||
@chapterClick="onChapterClick" | ||
></ChaptersList> | ||
</div> | ||
</li> | ||
</ol> | ||
</template> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
<script setup> | ||
import { ref, triggerRef, watchEffect } from 'vue'; | ||
import { VuePDF, getPDFDestination, usePDF } from '@tato30/vue-pdf'; | ||
import { withBase } from '@vuepress/client'; | ||
import ChaptersList from './ChaptersList.vue'; | ||
const { pdf, info } = usePDF(withBase('/example_045.pdf')) | ||
const eventValue = ref({}) | ||
const outlineTree = ref([]) | ||
watchEffect(() => { | ||
if (info.value.outline !== undefined) { | ||
outlineTree.value = info.value.outline.map(function convert(node) { | ||
return { | ||
title: node.title, | ||
destination: getPDFDestination(info.value.document, node.dest), | ||
items: node.items.map((item) => { | ||
return convert(item) | ||
}), | ||
} | ||
}) | ||
} | ||
}) | ||
triggerRef(info) | ||
function onChapterClick(value) { | ||
value.then(v => { | ||
console.log(v) | ||
eventValue.value = v | ||
}) | ||
} | ||
</script> | ||
|
||
<template> | ||
<div id="toc_wrapper"> | ||
<div class="toc"> | ||
<ChaptersList | ||
:items="outlineTree" | ||
@chapterClick="onChapterClick" | ||
> | ||
</ChaptersList> | ||
</div> | ||
<div> | ||
<div class="language-json" data-ext="json"> | ||
<pre class="language-json"><code>{{ eventValue }}</code></pre> | ||
</div> | ||
|
||
<div class="container"> | ||
<VuePDF :pdf="pdf" :scale="0.75" /> | ||
</div> | ||
</div> | ||
</div> | ||
</template> | ||
|
||
<style> | ||
#toc_wrapper { | ||
display: flex; | ||
flex-direction: row; | ||
} | ||
#toc_wrapper .toc { | ||
width: 300px; | ||
background-color: #eaeaea; | ||
} | ||
#toc_wrapper ol ol { | ||
padding-left: 20px; | ||
} | ||
#toc_wrapper ol { | ||
padding-left: 2em; | ||
} | ||
#toc_wrapper a { | ||
color: black; | ||
} | ||
</style> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
# Table of content | ||
|
||
```vue | ||
<script setup> | ||
import { ref, triggerRef, watchEffect } from 'vue'; | ||
import { VuePDF, getPDFDestination, usePDF } from '@tato30/vue-pdf'; | ||
import { withBase } from '@vuepress/client'; | ||
import ChaptersList from './ChaptersList.vue'; | ||
const { pdf, info } = usePDF(withBase('/example_045.pdf')) | ||
const outlineTree = ref([]) | ||
watchEffect(() => { | ||
if (info.value.outline !== undefined) { | ||
outlineTree.value = info.value.outline.map(function convert(node) { | ||
return { | ||
title: node.title, | ||
destination: getPDFDestination(info.value.document, node.dest), | ||
items: node.items.map((item) => { | ||
return convert(item) | ||
}), | ||
} | ||
}) | ||
} | ||
}) | ||
triggerRef(info) | ||
function onChapterClick(value) { | ||
value.then(v => { | ||
console.log(v) | ||
}) | ||
} | ||
</script> | ||
<template> | ||
<div id="toc_wrapper"> | ||
<div class="toc"> | ||
<ChaptersList | ||
:items="outlineTree" | ||
@chapterClick="onChapterClick" | ||
> | ||
</ChaptersList> | ||
</div> | ||
<div class="container"> | ||
<VuePDF :pdf="pdf" /> | ||
</div> | ||
</div> | ||
</template> | ||
``` | ||
<ClientOnly> | ||
<TOC /> | ||
</ClientOnly> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
/** | ||
* Written by Jason Harwig as part of PDFjs React Outline Viewer | ||
* Source: https://codesandbox.io/s/rp18w | ||
*/ | ||
import { PDFDocumentProxy, RefProxy } from "pdfjs-dist/types/src/display/api"; | ||
|
||
|
||
type Base<T, S> = {type: T, spec: S} | ||
// These are types from the PDF 1.7 reference manual; Adobe | ||
// Table 151 – Destination syntax | ||
// (Coordinates origin is bottom left of page) | ||
type XYZ = Base<'XYZ', [left: number, top: number, zoom: number]> | ||
type Fit = Base<'Fit', []> | ||
type FitH = Base<'FitH', [top: number]> | ||
type FitV = Base<'FitV', [left: number]> | ||
type FitR = Base<'FitR', [ left: number, bottom: number, right: number, top: number ]> | ||
type FitB = Base<'FitB', []> | ||
type FitBH = Base<'FitBH', [top: number]> | ||
type FitBV = Base<'FitBV', [left: number]> | ||
|
||
type PDFLocation = XYZ | Fit | FitH | FitV | FitR | FitB | FitBH | FitBV | ||
export interface PDFDestination { | ||
pageIndex: number | ||
location: PDFLocation | ||
} | ||
|
||
const isRefProxy = (obj: unknown): obj is RefProxy => | ||
Boolean(typeof obj === "object" && obj && "gen" in obj && "num" in obj); | ||
|
||
const getDestinationArray = async ( | ||
doc: PDFDocumentProxy, | ||
dest: string | any[] | null | ||
): Promise<any[] | null> => | ||
typeof dest === "string" ? doc.getDestination(dest) : dest; | ||
|
||
const getDestinationRef = async ( | ||
doc: PDFDocumentProxy, | ||
destArray: any[] | null | ||
): Promise<RefProxy | null> => { | ||
if (destArray && isRefProxy(destArray[0])) { | ||
return destArray[0]; | ||
} | ||
return null; | ||
}; | ||
|
||
const isXYZ = (obj: {type: string, spec: number[]}): obj is XYZ => obj.type === 'XYZ' && obj.spec.length === 3 | ||
const isFit = (obj: {type: string, spec: number[]}): obj is Fit => obj.type === 'Fit' && obj.spec.length === 0 | ||
const isFitH = (obj: {type: string, spec: number[]}): obj is FitH => obj.type === 'FitH' && obj.spec.length === 1 | ||
const isFitV = (obj: {type: string, spec: number[]}): obj is FitV => obj.type === 'FitV' && obj.spec.length === 1 | ||
const isFitR = (obj: {type: string, spec: number[]}): obj is FitR => obj.type === 'FitR' && obj.spec.length === 4 | ||
const isFitB = (obj: {type: string, spec: number[]}): obj is FitB => obj.type === 'FitB' && obj.spec.length === 0 | ||
const isFitBH = (obj: {type: string, spec: number[]}): obj is FitBH => obj.type === 'FitBH' && obj.spec.length === 1 | ||
const isFitBV = (obj: {type: string, spec: number[]}): obj is FitBV => obj.type === 'FitBV' && obj.spec.length === 1 | ||
|
||
|
||
const getLocation = (type: string, spec: number[]): PDFLocation | null => { | ||
const obj = {type, spec} | ||
if (isXYZ(obj)) return obj | ||
if (isFit(obj)) return obj | ||
if (isFitH(obj)) return obj | ||
if (isFitV(obj)) return obj | ||
if (isFitR(obj)) return obj | ||
if (isFitB(obj)) return obj | ||
if (isFitBH(obj)) return obj | ||
if (isFitBV(obj)) return obj | ||
console.warn("no location type found for ", type, spec) | ||
|
||
return null | ||
} | ||
|
||
const isSpecLike = (list: any[]): list is number[] => list && list.every(v => !isNaN(v)) | ||
|
||
export async function getPDFDestination(document: PDFDocumentProxy, destination: string | any[] | null): Promise<PDFDestination | null> { | ||
const destArray = await getDestinationArray(document, destination) | ||
const destRef = await getDestinationRef(document, destArray); | ||
if (!destRef || !destArray) return null; | ||
|
||
const pageIndex = await document.getPageIndex(destRef); | ||
const name = destArray[1].name | ||
const rest = destArray.slice(2) | ||
const location = isSpecLike(rest) ? getLocation(name, rest) : null | ||
|
||
return {pageIndex, location: location ?? {type: 'Fit', spec: []}}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters