Skip to content

Commit fab4ceb

Browse files
committed
add fetch font
tapout blur instead of dom blur
1 parent 91df44e commit fab4ceb

File tree

11 files changed

+144
-106
lines changed

11 files changed

+144
-106
lines changed
File renamed without changes.

src/App.svelte

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import Text from "./Text.svelte";
88
import Drawing from "./Drawing.svelte";
99
import DrawingCanvas from "./DrawingCanvas.svelte";
10+
import { fetchFont } from "./utils/prepareAssets.js";
1011
import {
1112
readAsArrayBuffer,
1213
readAsImage,
@@ -21,6 +22,8 @@
2122
let pages = [];
2223
let pagesScale = [];
2324
let allObjects = [];
25+
let currentFont = "Noto Sans TC";
26+
let focusId = null;
2427
let selectedPageIndex = -1;
2528
let saving = false;
2629
let addingDrawing = false;
@@ -31,6 +34,7 @@
3134
const pdfBlob = await res.blob();
3235
await addPDF(pdfBlob);
3336
selectedPageIndex = 0;
37+
fetchFont(currentFont);
3438
// const imgBlob = await (await fetch("/test.jpg")).blob();
3539
// addImage(imgBlob);
3640
// addTextField("測試 New Text Field!");
@@ -104,13 +108,14 @@
104108
}
105109
function addTextField(text = "新文字方塊 New Text Field") {
106110
const id = genID();
111+
fetchFont(currentFont);
107112
const object = {
108113
id,
109114
text,
110115
type: "text",
111116
size: 16,
112117
lineHeight: 1.4,
113-
fontFamily: "default font",
118+
fontFamily: currentFont,
114119
x: 100,
115120
y: 0
116121
};

src/PDFPage.svelte

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
<script>
2-
import { onMount, createEventDispatcher } from "svelte";
2+
import { onMount, onDestroy, createEventDispatcher } from "svelte";
33
export let page;
44
const dispatch = createEventDispatcher();
55
let canvas;
66
let width;
77
let height;
88
let clientWidth;
99
let mounted;
10-
$: if (mounted) {
11-
console.log(`Page container size changed: ${clientWidth}`);
10+
function measure() {
1211
dispatch("measure", {
1312
scale: canvas.clientWidth / width
1413
});
@@ -23,12 +22,13 @@
2322
canvasContext: context,
2423
viewport: viewport
2524
}).promise;
26-
mounted = true;
27-
dispatch("measure", {
28-
scale: canvas.clientWidth / width
29-
});
25+
measure();
26+
window.addEventListener("resize", measure);
3027
}
3128
onMount(render);
29+
onDestroy(() => {
30+
window.removeEventListener("resize", measure);
31+
});
3232
</script>
3333

3434
<div bind:clientWidth>

src/Portal.svelte

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<script>
2+
let portal;
3+
$: portal && document.body.appendChild(portal);
4+
</script>
5+
6+
<div bind:this={portal}>
7+
<slot />
8+
</div>

src/Text.svelte

Lines changed: 48 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
<script>
22
import { onMount, createEventDispatcher } from "svelte";
3+
import Toolbar from "./Toolbar.svelte";
34
import { pannable } from "./utils/pannable.js";
5+
import { tapout } from "./utils/tapout.js";
46
import { timeout } from "./utils/helper.js";
57
export let size;
68
export let text;
@@ -45,16 +47,12 @@
4547
operation = "edit";
4648
}
4749
async function onBlur() {
50+
if (operation !== "edit" || operation === "tool") return;
4851
sanitize();
4952
dispatch("update", {
5053
lines: extractLines()
5154
});
52-
// Give toolbar a chance to focus
53-
operation = "edited";
54-
await timeout();
55-
if (operation === "edited") {
56-
operation = "";
57-
}
55+
operation = "";
5856
}
5957
async function onPaste(e) {
6058
// get text only
@@ -99,20 +97,16 @@
9997
}
10098
}
10199
function onFocusTool() {
102-
operation = "edit";
100+
operation = "tool";
103101
}
104102
async function onBlurTool() {
103+
if (operation !== "tool" || operation === "edit") return;
105104
dispatch("update", {
106105
lines: extractLines(),
107106
lineHeight: _lineHeight,
108107
size: _size
109108
});
110-
operation = "edited";
111-
// Give text field a chance to focus
112-
await timeout();
113-
if (operation === "edited") {
114-
operation = "";
115-
}
109+
operation = "";
116110
}
117111
function sanitize() {
118112
let weirdNode;
@@ -144,83 +138,64 @@
144138
lines.push(lineText);
145139
return lines;
146140
}
147-
// image version text generator
148-
// function drawText() {
149-
// const height = editable.offsetHeight;
150-
// const width = editable.offsetWidth;
151-
// const fontHeight = _size * _lineHeight;
152-
// // prevent crop of last line
153-
// canvas.height = height + 10;
154-
// canvas.width = width;
155-
// const ctx = canvas.getContext("2d");
156-
// ctx.font = `${_size}px "${_fontFamily}"`;
157-
// const nodes = editable.childNodes;
158-
// // FIXME: This fix should rely on ascender and descender of the font instead.
159-
// let dy = fontHeight - (fontHeight - _size) / 2 - _size / 5;
160-
// let lineText = "";
161-
// for (let index = 0; index < nodes.length; index++) {
162-
// const node = nodes[index];
163-
// if (node.nodeName === "BR") {
164-
// ctx.fillText(lineText, 0, dy);
165-
// dy += fontHeight;
166-
// lineText = "";
167-
// } else {
168-
// // use textContext to trim strings like &npsp;
169-
// lineText += node.textContent;
170-
// }
171-
// }
172-
// if (lineText) {
173-
// ctx.fillText(lineText, 0, dy);
174-
// }
175-
// }
176141
onMount(render);
177142
</script>
178143

144+
<style>
145+
.editing {
146+
@apply pointer-events-none border-gray-800 border-dashed;
147+
}
148+
</style>
149+
179150
<svelte:options immutable={true} />
151+
{#if operation}
152+
<Toolbar>
153+
<div
154+
use:tapout
155+
on:tapout={onBlurTool}
156+
on:mousedown={onFocusTool}
157+
on:touchstart={onFocusTool}
158+
class=" h-full flex justify-center items-center bg-gray-200 border-b
159+
border-gray-300">
160+
<div class="mr-1 flex items-center">
161+
<img src="/line_height.svg" class="w-4 mr-1" alt="Line height" />
162+
<input
163+
type="number"
164+
min="1"
165+
step="0.1"
166+
class="text-sm w-10 text-center flex-shrink-0"
167+
bind:value={_lineHeight} />
168+
</div>
169+
<div class="flex">
170+
<img src="/text.svg" class="w-4 mr-1" alt="Font size" />
171+
<input
172+
type="number"
173+
min="12"
174+
step="1"
175+
class="text-sm w-10 text-center"
176+
bind:value={_size} />
177+
</div>
178+
</div>
179+
</Toolbar>
180+
{/if}
180181
<div
182+
use:tapout
183+
on:tapout={onBlur}
181184
class="absolute left-0 top-0 select-none"
182185
style="transform: translate({x + dx}px, {y + dy}px);">
183186
<div
184187
use:pannable
185188
on:panstart={handlePanStart}
186189
on:panmove={handlePanMove}
187190
on:panend={handlePanEnd}
188-
class="absolute w-full h-full cursor-grab border {operation === 'edit' ? 'border-dotted' : 'border-dashed'}
189-
border-gray-600"
191+
class="absolute w-full h-full cursor-grab border border-dotted
192+
border-gray-500"
190193
class:cursor-grab={!operation}
191194
class:cursor-grabbing={operation === 'move'}
192-
class:pointer-events-none={['edit', 'edited'].includes(operation)} />
193-
<!-- 4.25rem: When text is short and container is short, this element shrink. The number adjustment elements don't take space in no reason. -->
194-
<div
195-
class="absolute top-0 p-1 transform -translate-x-full bg-gray-400 rounded-sm"
196-
class:hidden={!['edit', 'edited'].includes(operation)}
197-
style="left: -10px; min-width: 4.25rem;">
198-
<div class="mb-1 flex items-center">
199-
<img src="/line_height.svg" class="w-4 mr-1" alt="Line height" />
200-
<input
201-
type="number"
202-
min="1"
203-
step="0.1"
204-
on:focus={onFocusTool}
205-
on:blur={onBlurTool}
206-
class="text-sm w-10 text-center flex-shrink-0"
207-
bind:value={_lineHeight} />
208-
</div>
209-
<div class="flex">
210-
<img src="/text.svg" class="w-4 mr-1" alt="Font size" />
211-
<input
212-
type="number"
213-
min="9"
214-
on:focus={onFocusTool}
215-
on:blur={onBlurTool}
216-
class="text-sm w-10 text-center"
217-
bind:value={_size} />
218-
</div>
219-
</div>
195+
class:editing={['edit', 'tool'].includes(operation)} />
220196
<div
221197
bind:this={editable}
222198
on:focus={onFocus}
223-
on:blur={onBlur}
224199
on:keydown={onKeydown}
225200
on:paste|preventDefault={onPaste}
226201
contenteditable="true"

src/Toolbar.svelte

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<script>
2+
import Portal from "./Portal.svelte";
3+
</script>
4+
5+
<Portal>
6+
<div class="fixed z-10 top-0 left-0 right-0 h-12">
7+
<slot />
8+
</div>
9+
</Portal>

src/main.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import App from './App.svelte';
2-
import prepareAssets from './utils/prepareAssets.js'
2+
import prepareAssets from './utils/prepareAssets.js';
33

4-
prepareAssets()
4+
prepareAssets();
55
const app = new App({
66
target: document.body,
77
});

src/utils/PDF.js

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import { readAsArrayBuffer } from './asyncReader.js';
2+
import { fetchFont } from './prepareAssets';
23
import { noop } from './helper.js';
34

45
export async function save(pdfFile, objects, name) {
56
const PDFLib = await window.getAsset('PDFLib');
67
const download = await window.getAsset('download');
7-
const makeFont = await window.getAsset('makeTextPDF');
8-
const defaultFont = await window.getAsset('defaultFont');
8+
const makeTextPDF = await window.getAsset('makeTextPDF');
99
let pdfDoc;
1010
try {
1111
pdfDoc = await PDFLib.PDFDocument.load(await readAsArrayBuffer(pdfFile));
@@ -40,17 +40,17 @@ export async function save(pdfFile, objects, name) {
4040
return noop;
4141
}
4242
} else if (object.type === 'text') {
43-
let { x, y, lines, lineHeight, size } = object;
44-
43+
let { x, y, lines, lineHeight, size, fontFamily } = object;
44+
const font = await fetchFont(fontFamily);
4545
const [textPage] = await pdfDoc.embedPdf(
46-
await makeFont({
46+
await makeTextPDF({
4747
lines,
4848
fontSize: size,
4949
lineHeight,
5050
width: pageWidth,
5151
height: pageHeight,
52-
font: defaultFont,
53-
dy: (size * lineHeight - size) / 2,
52+
font: font.buffer,
53+
dy: font.correction(size, lineHeight),
5454
})
5555
);
5656
return () =>

src/utils/fontConfig.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
export default {
2+
'Noto Sans TC': {
3+
src: '/NotoSansTC-Regular.woff2',
4+
correction(size, lineHeight) {
5+
return (size * lineHeight - size) / 2 - size / 4;
6+
},
7+
},
8+
CK: {
9+
src: '/CK.ttf', // 9.9 MB
10+
correction(size, lineHeight) {
11+
return (size * lineHeight - size) / 2;
12+
},
13+
},
14+
};

src/utils/prepareAssets.js

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import Fonts from './fontConfig.js';
2+
13
const scripts = [
24
{
35
name: 'pdfjsLib',
@@ -14,9 +16,10 @@ const scripts = [
1416
{ name: 'makeTextPDF', src: '/makeTextPDF.js' },
1517
];
1618

17-
export default async function prepareAssets() {
18-
const assets = {};
19-
window.getAsset = (scriptName) => assets[scriptName];
19+
const assets = {};
20+
window.getAsset = (scriptName) => assets[scriptName];
21+
22+
export default function prepareAssets() {
2023
// prepare scripts
2124
scripts.forEach(({ name, src }) => {
2225
assets[name] = new Promise((resolve, reject) => {
@@ -31,17 +34,22 @@ export default async function prepareAssets() {
3134
document.body.appendChild(script);
3235
});
3336
});
34-
// prepare font
35-
// 標楷體 9.9MB
36-
// assets['defaultFont'] = fetch('/CKs.ttf').then((r) => r.arrayBuffer());
37-
assets['defaultFont'] = fetch('/NotoSansTC-Regular.woff2').then((r) =>
38-
r.arrayBuffer()
39-
);
40-
const font = new FontFace(
41-
'default font',
42-
await window.getAsset('defaultFont')
43-
);
44-
font.display = 'swap';
45-
await font.load();
46-
document.fonts.add(font);
37+
}
38+
39+
const fonts = {};
40+
export function fetchFont(name) {
41+
if (fonts[name]) return fonts[name];
42+
const font = Fonts[name];
43+
if (!font) throw new Error(`Font '${name}' not exists.`);
44+
fonts[name] = fetch(font.src)
45+
.then((r) => r.arrayBuffer())
46+
.then((fontBuffer) => {
47+
const fontFace = new FontFace(name, fontBuffer);
48+
fontFace.display = 'swap';
49+
fontFace.load().then(() => document.fonts.add(fontFace));
50+
return {
51+
...font,
52+
buffer: fontBuffer,
53+
};
54+
});
4755
}

0 commit comments

Comments
 (0)