@jasy/pdf 1.0.0-beta.1 → 1.0.0-beta.3
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/dist/elements/text-element.d.ts +8 -0
- package/dist/elements/text-element.js +25 -2
- package/dist/text/advance.js +81 -7
- package/dist/utils/afm-parser.d.ts +1 -1
- package/dist/utils/afm-parser.js +24 -16
- package/dist/utils/font-metrics.d.ts +3 -0
- package/dist/utils/pdf-object-manager.d.ts +6 -0
- package/dist/utils/pdf-object-manager.js +30 -8
- package/dist/utils/utf8-to-windows1252-encoder.js +5 -3
- package/package.json +1 -1
|
@@ -130,6 +130,14 @@ export declare class TextElement extends SizedPDFElement implements Fragmentable
|
|
|
130
130
|
private readonly role?;
|
|
131
131
|
constructor({ fontSize, content, fontFamily, fontStyle, color, textAlignment, maxLines, orphans, widows, overflow, lineHeight, underline, strikethrough, skipInk, letterSpacing, direction, fontFallback, textTransform, wordSpacing, textIndent, breakWord, hyphenate, ligatures, role, }: TextElementParams);
|
|
132
132
|
private resolveStyle;
|
|
133
|
+
/**
|
|
134
|
+
* The last `display(metrics)` result, with what it depended on - recasing, coverage and the fallback
|
|
135
|
+
* split each walk the whole text, and every measure, break and draw asks again.
|
|
136
|
+
*
|
|
137
|
+
* COMPARED, not cleared in `resolveStyle`: that runs on every pass and resolves to the same values,
|
|
138
|
+
* so clearing would drop the answer exactly when it is about to be reused.
|
|
139
|
+
*/
|
|
140
|
+
private displayed?;
|
|
133
141
|
/** Justified lines may be squeezed to keep a word; every other alignment gets no slack. Read by the
|
|
134
142
|
* breaker in BOTH passes, or a measured line and a drawn one would disagree about where it ends. */
|
|
135
143
|
/**
|
|
@@ -73,14 +73,37 @@ export class TextElement extends SizedPDFElement {
|
|
|
73
73
|
* same width in most fonts).
|
|
74
74
|
*/
|
|
75
75
|
display(metrics) {
|
|
76
|
+
const memo = this.displayed;
|
|
77
|
+
if (metrics &&
|
|
78
|
+
memo &&
|
|
79
|
+
memo.metrics === metrics &&
|
|
80
|
+
// A face registered since then changes what is drawable and how the fallback stack splits.
|
|
81
|
+
memo.epoch === (metrics.fontEpoch ?? 0) &&
|
|
82
|
+
memo.family === this.fontFamily &&
|
|
83
|
+
memo.style === this.fontStyle &&
|
|
84
|
+
memo.transform === this.textTransform &&
|
|
85
|
+
memo.fallback === this.fontFallback)
|
|
86
|
+
return memo.value;
|
|
76
87
|
const cased = this.recased();
|
|
77
88
|
// A SPAN may bring its own stack, so the element's being empty is not enough to skip the pass.
|
|
78
89
|
const anyStack = this.fontFallback.length > 0 ||
|
|
79
90
|
(typeof cased !== "string" && cased.some((seg) => (seg.fontFallback?.length ?? 0) > 0));
|
|
80
91
|
if (!metrics)
|
|
81
92
|
return cased;
|
|
93
|
+
const remember = (value) => {
|
|
94
|
+
this.displayed = {
|
|
95
|
+
metrics,
|
|
96
|
+
epoch: metrics.fontEpoch ?? 0,
|
|
97
|
+
family: this.fontFamily,
|
|
98
|
+
style: this.fontStyle,
|
|
99
|
+
transform: this.textTransform,
|
|
100
|
+
fallback: this.fontFallback,
|
|
101
|
+
value,
|
|
102
|
+
};
|
|
103
|
+
return value;
|
|
104
|
+
};
|
|
82
105
|
if (!anyStack)
|
|
83
|
-
return this.covered(cased, metrics);
|
|
106
|
+
return remember(this.covered(cased, metrics));
|
|
84
107
|
// Font fallback turns the content into spans - one per family - which every later pass already
|
|
85
108
|
// knows how to handle. Nothing new downstream.
|
|
86
109
|
const pieces = typeof cased === "string" ? [{ content: cased }] : cased;
|
|
@@ -93,7 +116,7 @@ export class TextElement extends SizedPDFElement {
|
|
|
93
116
|
for (const run of runs)
|
|
94
117
|
out.push({ ...seg, content: run.text, fontFamily: run.fontFamily });
|
|
95
118
|
}
|
|
96
|
-
return this.covered(out, metrics);
|
|
119
|
+
return remember(this.covered(out, metrics));
|
|
97
120
|
}
|
|
98
121
|
/**
|
|
99
122
|
* Drops or substitutes whatever the RESOLVED font cannot draw. Runs last, after the fallback stack
|
package/dist/text/advance.js
CHANGED
|
@@ -26,21 +26,95 @@ export function codePointCount(text) {
|
|
|
26
26
|
n++;
|
|
27
27
|
return n;
|
|
28
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* Glyph widths plus kerning, memoised per metrics object - the line breaker asks for the same run
|
|
31
|
+
* many times over.
|
|
32
|
+
*
|
|
33
|
+
* Keyed by nested maps on primitives, never a composed string key: building one allocates per call,
|
|
34
|
+
* which costs more than the lookup saves.
|
|
35
|
+
*
|
|
36
|
+
* `letterSpacing` is not in the key: it is added outside, so a spaced and an unspaced run share the
|
|
37
|
+
* width. Font size IS in it - scaling a cached em width drifts in the last bit, which is enough to
|
|
38
|
+
* move a line break. Kerning is a flag on the metrics rather than a key, so the entry records it and
|
|
39
|
+
* is thrown away when it flips; the same document can be rendered twice with different options.
|
|
40
|
+
*/
|
|
41
|
+
const ADVANCE_CACHE = new WeakMap();
|
|
42
|
+
/**
|
|
43
|
+
* The innermost map a `RunFont` resolves to, remembered on the font object itself. The breaker builds
|
|
44
|
+
* one font per run and asks for every word with it, so without this each word walks five maps of which
|
|
45
|
+
* four hold a single entry.
|
|
46
|
+
*/
|
|
47
|
+
const BY_FONT = new WeakMap();
|
|
48
|
+
function baseAdvance(metrics, text, font) {
|
|
49
|
+
let entry = ADVANCE_CACHE.get(metrics);
|
|
50
|
+
const epoch = metrics.fontEpoch ?? 0;
|
|
51
|
+
const kerning = metrics.kerningEnabled;
|
|
52
|
+
// A face registered since we measured makes a family name mean something else; a flipped kerning
|
|
53
|
+
// flag makes every width wrong. Either way the answers are stale.
|
|
54
|
+
if (!entry || entry.epoch !== epoch || entry.kerning !== kerning) {
|
|
55
|
+
entry = { epoch, kerning, byFamily: new Map() };
|
|
56
|
+
ADVANCE_CACHE.set(metrics, entry);
|
|
57
|
+
}
|
|
58
|
+
// Same font object as last time, and the metrics entry has not been replaced: the map is already known.
|
|
59
|
+
// Its FIELDS are compared, not just its identity: a caller that reuses one object and changes the
|
|
60
|
+
// size on it would otherwise be handed the previous size's map.
|
|
61
|
+
const known = BY_FONT.get(font);
|
|
62
|
+
if (known?.entry === entry &&
|
|
63
|
+
known.family === font.fontFamily &&
|
|
64
|
+
known.size === font.fontSize &&
|
|
65
|
+
known.style === font.fontStyle &&
|
|
66
|
+
known.ligatures === font.ligatures) {
|
|
67
|
+
const seen = known.byText.get(text);
|
|
68
|
+
if (seen !== undefined)
|
|
69
|
+
return seen;
|
|
70
|
+
return measureAndStore(metrics, text, font, known.byText);
|
|
71
|
+
}
|
|
72
|
+
let byStyle = entry.byFamily.get(font.fontFamily);
|
|
73
|
+
if (!byStyle)
|
|
74
|
+
entry.byFamily.set(font.fontFamily, (byStyle = new Map()));
|
|
75
|
+
let byLigatures = byStyle.get(font.fontStyle);
|
|
76
|
+
if (!byLigatures)
|
|
77
|
+
byStyle.set(font.fontStyle, (byLigatures = new Map()));
|
|
78
|
+
let bySize = byLigatures.get(font.ligatures);
|
|
79
|
+
if (!bySize)
|
|
80
|
+
byLigatures.set(font.ligatures, (bySize = new Map()));
|
|
81
|
+
let byText = bySize.get(font.fontSize);
|
|
82
|
+
if (!byText)
|
|
83
|
+
bySize.set(font.fontSize, (byText = new Map()));
|
|
84
|
+
BY_FONT.set(font, {
|
|
85
|
+
entry,
|
|
86
|
+
family: font.fontFamily,
|
|
87
|
+
size: font.fontSize,
|
|
88
|
+
style: font.fontStyle,
|
|
89
|
+
ligatures: font.ligatures,
|
|
90
|
+
byText,
|
|
91
|
+
});
|
|
92
|
+
const hit = byText.get(text);
|
|
93
|
+
if (hit !== undefined)
|
|
94
|
+
return hit;
|
|
95
|
+
return measureAndStore(metrics, text, font, byText);
|
|
96
|
+
}
|
|
97
|
+
/** The measurement itself, once a miss has found the map it belongs in. */
|
|
98
|
+
function measureAndStore(metrics, text, font, byText) {
|
|
99
|
+
let advance = metrics.getStringWidth(text, font.fontFamily, font.fontSize, font.fontStyle, font.ligatures);
|
|
100
|
+
if (metrics.kerningEnabled) {
|
|
101
|
+
let units = 0;
|
|
102
|
+
for (const k of metrics.getKernPairs(text, font.fontFamily, font.fontStyle, font.ligatures))
|
|
103
|
+
units += k;
|
|
104
|
+
advance += (units / 1000) * font.fontSize; // kern units are em/1000
|
|
105
|
+
}
|
|
106
|
+
byText.set(text, advance);
|
|
107
|
+
return advance;
|
|
108
|
+
}
|
|
29
109
|
/** The advance of `text` in points: the plain glyph widths, plus kerning (if the document has it on),
|
|
30
110
|
* plus one `letterSpacing` per code point. */
|
|
31
111
|
export function runAdvance(metrics, text, font, letterSpacing = 0) {
|
|
32
|
-
let advance = metrics
|
|
112
|
+
let advance = baseAdvance(metrics, text, font);
|
|
33
113
|
if (letterSpacing !== 0) {
|
|
34
114
|
// Per DRAWN glyph, which is what `Tc` applies to; only a ligature makes that differ.
|
|
35
115
|
const glyphs = metrics.shapedGlyphCount?.(text, font.fontFamily, font.fontStyle, font.ligatures) ??
|
|
36
116
|
codePointCount(text);
|
|
37
117
|
advance += glyphs * letterSpacing;
|
|
38
118
|
}
|
|
39
|
-
if (metrics.kerningEnabled) {
|
|
40
|
-
let units = 0;
|
|
41
|
-
for (const k of metrics.getKernPairs(text, font.fontFamily, font.fontStyle, font.ligatures))
|
|
42
|
-
units += k;
|
|
43
|
-
advance += (units / 1000) * font.fontSize; // kern units are em/1000
|
|
44
|
-
}
|
|
45
119
|
return advance;
|
|
46
120
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { FontVerticals } from "../text/line-metrics.js";
|
|
2
2
|
import type { FontDecoration } from "../text/text-decoration.js";
|
|
3
|
+
export declare function standardParser(fullName: string, data: string): AFMParser;
|
|
3
4
|
export declare class AFMParser {
|
|
4
5
|
private advanceWidths;
|
|
5
6
|
private kerningPairs;
|
|
@@ -12,7 +13,6 @@ export declare class AFMParser {
|
|
|
12
13
|
private xHeight;
|
|
13
14
|
private cachedDecoration?;
|
|
14
15
|
constructor(afmData: string);
|
|
15
|
-
private loadGlyphList;
|
|
16
16
|
private getGlyphName;
|
|
17
17
|
private parseAFMData;
|
|
18
18
|
private parseCharMetrics;
|
package/dist/utils/afm-parser.js
CHANGED
|
@@ -1,9 +1,32 @@
|
|
|
1
1
|
import { AGL } from "../assets/font-data.js";
|
|
2
|
+
/** The Adobe Glyph List: code point -> glyph name. Constant, so it is parsed once per process and
|
|
3
|
+
* shared by every face rather than rebuilt per `AFMParser`. */
|
|
4
|
+
let sharedGlyphMap;
|
|
5
|
+
function glyphList() {
|
|
6
|
+
if (sharedGlyphMap)
|
|
7
|
+
return sharedGlyphMap;
|
|
8
|
+
const map = {};
|
|
9
|
+
for (const line of AGL.split("\n")) {
|
|
10
|
+
const parts = line.trim().split(";");
|
|
11
|
+
if (parts.length >= 2)
|
|
12
|
+
map[String.fromCharCode(parseInt(parts[0], 16))] = parts[1];
|
|
13
|
+
}
|
|
14
|
+
return (sharedGlyphMap = map);
|
|
15
|
+
}
|
|
16
|
+
/** One parser per standard-14 face for the whole process: the AFM data is a constant and a parsed
|
|
17
|
+
* face is read-only, so every document can share one. */
|
|
18
|
+
const STANDARD_PARSERS = new Map();
|
|
19
|
+
export function standardParser(fullName, data) {
|
|
20
|
+
let parser = STANDARD_PARSERS.get(fullName);
|
|
21
|
+
if (!parser)
|
|
22
|
+
STANDARD_PARSERS.set(fullName, (parser = new AFMParser(data)));
|
|
23
|
+
return parser;
|
|
24
|
+
}
|
|
2
25
|
export class AFMParser {
|
|
3
26
|
constructor(afmData) {
|
|
4
27
|
this.advanceWidths = {};
|
|
5
28
|
this.kerningPairs = {};
|
|
6
|
-
this.glyphMap =
|
|
29
|
+
this.glyphMap = glyphList();
|
|
7
30
|
// The font's bounding box in glyph space (1000 units / em), from the AFM header. This - not the
|
|
8
31
|
// `Ascender` line - is what a line box is built from; see `verticals()`.
|
|
9
32
|
this.bbox = [0, 0, 0, 0];
|
|
@@ -14,21 +37,6 @@ export class AFMParser {
|
|
|
14
37
|
this.capHeight = 0;
|
|
15
38
|
this.xHeight = 0;
|
|
16
39
|
this.parseAFMData(afmData);
|
|
17
|
-
this.loadGlyphList();
|
|
18
|
-
}
|
|
19
|
-
loadGlyphList() {
|
|
20
|
-
const lines = AGL.split("\n");
|
|
21
|
-
for (const line of lines) {
|
|
22
|
-
const parts = line.trim().split(";");
|
|
23
|
-
if (parts.length >= 2) {
|
|
24
|
-
const unicodeHex = parts[0];
|
|
25
|
-
const glyphName = parts[1];
|
|
26
|
-
// Converts the Unicode hex code into the corresponding character
|
|
27
|
-
const unicodeChar = String.fromCharCode(parseInt(unicodeHex, 16));
|
|
28
|
-
// Adds the character as the key and the glyph name as the value in the list
|
|
29
|
-
this.glyphMap[unicodeChar] = glyphName;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
40
|
}
|
|
33
41
|
getGlyphName(char) {
|
|
34
42
|
return this.glyphMap[char] || char;
|
|
@@ -20,6 +20,9 @@ export interface FontMetrics {
|
|
|
20
20
|
/** Whether kerning is on for this document. `runAdvance` reads it so a measured advance matches the
|
|
21
21
|
* drawn one (the backend emits a `TJ` under the same flag). */
|
|
22
22
|
readonly kerningEnabled: boolean;
|
|
23
|
+
/** Bumped whenever a face is registered, so a memoised measurement knows to drop it: the same family
|
|
24
|
+
* name can mean a different face afterwards. Optional - leaving it out means never invalidating. */
|
|
25
|
+
readonly fontEpoch?: number;
|
|
23
26
|
/** Per-adjacent-pair kerning of `text`, in em/1000 (negative tightens); length `codePoints - 1`,
|
|
24
27
|
* zero next to a space. Only meaningful when `kerningEnabled`. */
|
|
25
28
|
getKernPairs(text: string, fontFamily: string, fontStyle: FontStyle, ligatures?: boolean): number[];
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { OverflowPolicy } from "../layout/fragmentation.js";
|
|
2
2
|
import { TTFParser } from "./ttf-parser.js";
|
|
3
3
|
import { type ShapedGlyph } from "../text/shape.js";
|
|
4
|
+
export declare function featureKey(f: readonly string[]): string;
|
|
4
5
|
import type { SecurityHandler } from "../crypto/security-handler.js";
|
|
5
6
|
import type { Gradient } from "../ir/display-list.js";
|
|
6
7
|
import type { FontVerticals } from "../text/line-metrics.js";
|
|
@@ -40,7 +41,12 @@ export declare class PDFObjectManager implements FontMetrics {
|
|
|
40
41
|
private verticalsCache;
|
|
41
42
|
private decorationCache;
|
|
42
43
|
private customFontEmit;
|
|
44
|
+
/** Shaped runs, cached face -> feature set -> text. Nested, not one map under a composed key: that
|
|
45
|
+
* key would carry the whole run's text, so every lookup would copy it into a fresh string. */
|
|
43
46
|
private shapeCache;
|
|
47
|
+
/** Bumped by every font registration; `runAdvance` watches it to drop memoised widths, since a
|
|
48
|
+
* family name can mean a different face afterwards. */
|
|
49
|
+
fontEpoch: number;
|
|
44
50
|
private shapedOrigins;
|
|
45
51
|
private attachments;
|
|
46
52
|
private xmpMetadata?;
|
|
@@ -3,7 +3,7 @@ import { STANDARD_AFM } from "../assets/font-data.js";
|
|
|
3
3
|
import { md5, md5Hex } from "./md5.js";
|
|
4
4
|
import { zlibSync } from "fflate";
|
|
5
5
|
import { bytesFromLatin1, latin1FromBytes } from "./bytes.js";
|
|
6
|
-
import {
|
|
6
|
+
import { standardParser } from "./afm-parser.js";
|
|
7
7
|
import { mergeSpans, TTFParser } from "./ttf-parser.js";
|
|
8
8
|
import { WoffError, isWoff, woffToSfnt } from "./woff.js";
|
|
9
9
|
import { isWoff2 } from "./woff2.js";
|
|
@@ -13,8 +13,18 @@ import { shapeRun } from "../text/shape.js";
|
|
|
13
13
|
* drew for it. On by default like kerning - CSS, browsers and every other renderer do the same,
|
|
14
14
|
* and nobody should have to know the word to get text that is set properly. */
|
|
15
15
|
const LATIN_FEATURES = ["liga"];
|
|
16
|
-
/**
|
|
17
|
-
const
|
|
16
|
+
/** Stable, so `features(false)` does not allocate per call. */
|
|
17
|
+
const NO_FEATURES = [];
|
|
18
|
+
/** A feature set's cache key. Memoised only for the two arrays this module owns - a caller's array is
|
|
19
|
+
* joined every time, since holding on to its key would outlive a mutation of it. */
|
|
20
|
+
const OWN_FEATURE_KEYS = new Map([
|
|
21
|
+
[LATIN_FEATURES, LATIN_FEATURES.join(",")],
|
|
22
|
+
[NO_FEATURES, ""],
|
|
23
|
+
]);
|
|
24
|
+
export function featureKey(f) {
|
|
25
|
+
return OWN_FEATURE_KEYS.get(f) ?? f.join(",");
|
|
26
|
+
}
|
|
27
|
+
const features = (ligatures) => ligatures ? LATIN_FEATURES : NO_FEATURES;
|
|
18
28
|
import { getArrayBuffer, isWindows1252 } from "./utf8-to-windows1252-encoder.js";
|
|
19
29
|
import { isEmojiCodePoint } from "../text/emoji-codepoints.js";
|
|
20
30
|
import { StructTree } from "./struct-tree.js";
|
|
@@ -171,7 +181,12 @@ export class PDFObjectManager {
|
|
|
171
181
|
// program is subsetted and the objects filled in by finalizeCustomFonts(), after the render pass.
|
|
172
182
|
this.customFontEmit = new Map();
|
|
173
183
|
// Shaped runs, keyed by font + text; `null` means "asked, and there was nothing to shape".
|
|
184
|
+
/** Shaped runs, cached face -> feature set -> text. Nested, not one map under a composed key: that
|
|
185
|
+
* key would carry the whole run's text, so every lookup would copy it into a fresh string. */
|
|
174
186
|
this.shapeCache = new Map();
|
|
187
|
+
/** Bumped by every font registration; `runAdvance` watches it to drop memoised widths, since a
|
|
188
|
+
* family name can mean a different face afterwards. */
|
|
189
|
+
this.fontEpoch = 0;
|
|
175
190
|
// Per font, glyph -> the code points it stands for. A shaped glyph is often absent from the cmap (a
|
|
176
191
|
// ligature) or maps back to a presentation form, so `ToUnicode` cannot come from the cmap alone.
|
|
177
192
|
this.shapedOrigins = new Map();
|
|
@@ -550,6 +565,7 @@ endstream`;
|
|
|
550
565
|
}
|
|
551
566
|
// Registers a font
|
|
552
567
|
registerFont(fontName, fontStyle = FontStyle.Normal, fullName = fontName) {
|
|
568
|
+
this.fontEpoch++;
|
|
553
569
|
if (this.fonts.hasFont(fontName, fontStyle)) {
|
|
554
570
|
return this.fonts.getFont(fontName, fontStyle); // Already exists? Return it!
|
|
555
571
|
}
|
|
@@ -561,7 +577,7 @@ endstream`;
|
|
|
561
577
|
fontName,
|
|
562
578
|
fontStyle,
|
|
563
579
|
fullFontName: fullName,
|
|
564
|
-
parser:
|
|
580
|
+
parser: standardParser(fullName, data),
|
|
565
581
|
});
|
|
566
582
|
}
|
|
567
583
|
const resourceNumber = this.objects.length + 1; // New resource number
|
|
@@ -580,6 +596,7 @@ endstream`;
|
|
|
580
596
|
// metrics and emits its PDF font objects. All variants share the family `name`; bold/italic are
|
|
581
597
|
// separate .ttf files registered under the same name with a different style.
|
|
582
598
|
registerCustomFont(name, data, style = FontStyle.Normal) {
|
|
599
|
+
this.fontEpoch++;
|
|
583
600
|
let byStyle = this.customFonts.get(name);
|
|
584
601
|
if (!byStyle) {
|
|
585
602
|
byStyle = new Map();
|
|
@@ -767,13 +784,18 @@ endstream`;
|
|
|
767
784
|
const resolved = this.resolveCustomStyle(fontFamily, fontStyle);
|
|
768
785
|
if (!resolved)
|
|
769
786
|
return undefined;
|
|
770
|
-
const
|
|
771
|
-
|
|
787
|
+
const ttf = this.customFonts.get(fontFamily).get(resolved);
|
|
788
|
+
let byFeature = this.shapeCache.get(ttf);
|
|
789
|
+
if (!byFeature)
|
|
790
|
+
this.shapeCache.set(ttf, (byFeature = new Map()));
|
|
791
|
+
let byText = byFeature.get(featureKey(features));
|
|
792
|
+
if (!byText)
|
|
793
|
+
byFeature.set(featureKey(features), (byText = new Map()));
|
|
794
|
+
const cached = byText.get(text);
|
|
772
795
|
if (cached !== undefined)
|
|
773
796
|
return cached ?? undefined;
|
|
774
|
-
const ttf = this.customFonts.get(fontFamily).get(resolved);
|
|
775
797
|
const shaped = shapeRun([...text].map((c) => c.codePointAt(0)), ttf, features) ?? null;
|
|
776
|
-
|
|
798
|
+
byText.set(text, shaped);
|
|
777
799
|
if (shaped) {
|
|
778
800
|
// Recorded at shaping time - the only moment the glyph and its code points are known together.
|
|
779
801
|
const fontKey = this.customKey(fontFamily, resolved);
|
|
@@ -39,9 +39,11 @@ const CP1252_FROM_UNICODE = {
|
|
|
39
39
|
* except the C1 range, which the table above fills with printable punctuation instead.
|
|
40
40
|
*/
|
|
41
41
|
export function isWindows1252(codePoint) {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
42
|
+
// Latin-1 first: every key in the table above is > 0xff, so this range can never be in it. Order
|
|
43
|
+
// matters - this runs per character of every measured string, and the object lookup is the slow half.
|
|
44
|
+
if (codePoint <= 0xff)
|
|
45
|
+
return codePoint < 0x80 || codePoint > 0x9f;
|
|
46
|
+
return codePoint in CP1252_FROM_UNICODE;
|
|
45
47
|
}
|
|
46
48
|
/** Encodes a JavaScript string to a Windows-1252 byte buffer (the PDF text encoding). */
|
|
47
49
|
export function getArrayBuffer(data) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jasy/pdf",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.3",
|
|
4
4
|
"description": "Declarative, component-based PDF generation in pure TypeScript - Flutter-style components, real AFM font metrics, a hand-rolled PDF writer. No headless browser, no Java.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"declarative",
|