Comparing version 2.0.0-alpha.6 to 2.0.0-alpha.7
# Changelog | ||
All notable changes to this project will be documented in this file. | ||
## 2.0.0-alpha.6 | ||
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) | ||
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). | ||
## [2.0.0-alpha.7] - 2018-05-03 | ||
### Deprecated | ||
- creating AFM fonts is deprecated `new Font(require('pdfjs/font/Helvetica.json'))`, instead load instances directly `require('pdfjs/font/Helvetica')` | ||
### Added | ||
- Added type definitions for TypeScript #91 | ||
- Added support for parsing Object Streams | ||
### Changed | ||
- Expose Document class instead an anonymous class #92 | ||
- Improved ergonomics of loading AFM fonts (`require('pdfjs/font/Helvetica')` instead of `new Font(require('pdfjs/font/Helvetica.json'))`) | ||
- The `font` option when creating a `new Document` is now optional and defaults to Helvetica | ||
### Fixed | ||
- Tests should now run on windows #78 | ||
## [2.0.0-alpha.6] - 2018-03-27 | ||
### Fixed | ||
- fix kerning for OTF fonts #84 | ||
## 2.0.0-alpha.5 | ||
## [2.0.0-alpha.5] - 2018-03-26 | ||
### Added | ||
- implement font kerning (with a [minor limitation](https://github.com/rkusa/pdfjs/issues/82#issuecomment-376072547)) | ||
- implement font kerning (with a [minor limitation](https://github.com/rkusa/pdfjs/issues/82#issuecomment-376072547)) | ||
### Fixed | ||
- fixed PDF text not being printed when using macOS print dialog #83 (as a consequence, set PDF version of documents to 1.6) | ||
## 2.0.0-alpha.4 | ||
## [2.0.0-alpha.4] - 2018-03-21 | ||
### Fixed | ||
- fix combination of multiple TTF/OTF fonts in one line #81 | ||
## 2.0.0-alpha.3 | ||
## [2.0.0-alpha.3] - 2017-05-12 | ||
### Added | ||
- Text decoration underline and strikethrough | ||
- implement text decoration underline and strikethrough | ||
### Fixed | ||
- fix font re-use to not include glyphs from other documents |
@@ -53,3 +53,3 @@ 'use strict' | ||
// init default styling opts | ||
this.defaultFont = opts.font | ||
this.defaultFont = opts.font || require('../font/Helvetica') | ||
this.defaultFontSize = opts.fontSize || 11 | ||
@@ -123,2 +123,4 @@ this.defaultColor = opts.color && util.colorToRgb(opts.color) || [0, 0, 0] | ||
this._next() | ||
Fragment.prototype._init.call(this, this, this); | ||
} | ||
@@ -435,3 +437,6 @@ | ||
this._xref.add(content._object.id, this._length, content._object) | ||
this._xref.add(content._object.id, { | ||
offset: this._length, | ||
obj: content._object, | ||
}) | ||
@@ -490,3 +495,6 @@ let chunk = content._object.id + ' ' + content._object.rev + ' obj\n' | ||
this._xref.add(object.id, this._length, object) | ||
this._xref.add(object.id, { | ||
offset: this._length, | ||
obj: object, | ||
}) | ||
return this._write(object.toString() + '\n\n') | ||
@@ -498,2 +506,5 @@ } | ||
async end() { | ||
await Fragment.prototype.end.call(this); | ||
await this._next() | ||
@@ -549,69 +560,3 @@ await this._endPage() | ||
} | ||
} | ||
class AliasGenerator { | ||
constructor() { | ||
this.nextId = {} | ||
this.blocked = new Set() | ||
} | ||
next(prefix) { | ||
if (!(prefix in this.nextId)) { | ||
this.nextId[prefix] = 1 | ||
} | ||
let next | ||
do { | ||
next = prefix + this.nextId[prefix]++ | ||
} while (this.blocked.has(next)) | ||
return next | ||
} | ||
block(alias) { | ||
alias = String(alias) | ||
if (alias[0] === '/') { | ||
alias = alias.slice(1) | ||
} | ||
this.blocked.add(alias) | ||
} | ||
isBlocked(alias) { | ||
alias = String(alias) | ||
if (alias[0] === '/') { | ||
alias = alias.slice(1) | ||
} | ||
return this.blocked.has(alias) | ||
} | ||
reset(prefix) { | ||
this.nextId[prefix] = 1 | ||
} | ||
} | ||
module.exports = class extends Fragment { | ||
constructor(opts) { | ||
const doc = new Document(opts) | ||
super(doc, doc) | ||
} | ||
get info() { | ||
return this._doc.info | ||
} | ||
pipe(dest, opts) { | ||
return this._doc.pipe(dest, opts) | ||
} | ||
on() { | ||
return this._doc.on.apply(this._doc, arguments) | ||
} | ||
async end() { | ||
await Fragment.prototype.end.call(this) | ||
await this._doc.end() | ||
} | ||
asBuffer(callback) { | ||
@@ -631,9 +576,10 @@ let p = new Promise((resolve, reject) => { | ||
header() { | ||
const Header = require('./header') | ||
const ctx = new Header(this._doc, this) | ||
const ctx = new Header(this, this) | ||
this._begin(ctx) | ||
this._pending.push(() => { | ||
this._doc._header = ctx | ||
this._header = ctx | ||
return ctx._start() | ||
@@ -647,7 +593,7 @@ }) | ||
const Footer = require('./footer') | ||
const ctx = new Footer(this._doc, this) | ||
const ctx = new Footer(this, this) | ||
this._begin(ctx) | ||
this._pending.push(() => { | ||
this._doc._footer = ctx | ||
this._footer = ctx | ||
return ctx._start() | ||
@@ -665,3 +611,3 @@ }) | ||
this._begin(null) | ||
this._pending.push(() => external.write(this._doc)) | ||
this._pending.push(() => external.write(this)) | ||
} | ||
@@ -679,3 +625,3 @@ | ||
this._begin(null) | ||
this._pending.push(() => external.write(this._doc, page)) | ||
this._pending.push(() => external.write(this, page)) | ||
} | ||
@@ -689,4 +635,62 @@ | ||
this._begin(null) | ||
this._pending.push(() => external.setAsTemplate(this._doc)) | ||
this._pending.push(() => external.setAsTemplate(this)) | ||
} | ||
} | ||
Object.assign(Document.prototype, { | ||
_begin: Fragment.prototype._begin, | ||
_end: Fragment.prototype._end, | ||
_opts: Fragment.prototype._opts, | ||
text: Fragment.prototype.text, | ||
cell: Fragment.prototype.cell, | ||
table: Fragment.prototype.table, | ||
image: Fragment.prototype.image, | ||
pageBreak: Fragment.prototype.pageBreak, | ||
op: Fragment.prototype.op, | ||
destination: Fragment.prototype.destination, | ||
}) | ||
class AliasGenerator { | ||
constructor() { | ||
this.nextId = {} | ||
this.blocked = new Set() | ||
} | ||
next(prefix) { | ||
if (!(prefix in this.nextId)) { | ||
this.nextId[prefix] = 1 | ||
} | ||
let next | ||
do { | ||
next = prefix + this.nextId[prefix]++ | ||
} while (this.blocked.has(next)) | ||
return next | ||
} | ||
block(alias) { | ||
alias = String(alias) | ||
if (alias[0] === '/') { | ||
alias = alias.slice(1) | ||
} | ||
this.blocked.add(alias) | ||
} | ||
isBlocked(alias) { | ||
alias = String(alias) | ||
if (alias[0] === '/') { | ||
alias = alias.slice(1) | ||
} | ||
return this.blocked.has(alias) | ||
} | ||
reset(prefix) { | ||
this.nextId[prefix] = 1 | ||
} | ||
} | ||
module.exports = Document |
@@ -11,2 +11,3 @@ 'use strict' | ||
} else { | ||
console.warn("Manual construction of AFM fonts will be removed in the next version (use `require('pdfjs/font/Helvetica')` instead of `new Font(require('pdfjs/font/Helvetica.json'))`") | ||
return new AFMFont(arg) | ||
@@ -13,0 +14,0 @@ } |
@@ -46,3 +46,2 @@ 'use strict' | ||
} | ||
lexer.skipSpace(1, trial) | ||
@@ -69,2 +68,14 @@ const generation = lexer.readNumber(trial) | ||
const obj = PDFObject.parseInner(xref, lexer) | ||
lexer.skipWhitespace(null, true) | ||
if (lexer.readString(3) !== 'end') { | ||
throw new Error('Invalid object: `end` not found') | ||
} | ||
return obj | ||
} | ||
static parseInner(xref, lexer) { | ||
const value = PDFValue.parse(xref, lexer, true) | ||
@@ -111,8 +122,2 @@ if (value === undefined) { | ||
lexer.skipWhitespace(null, true) | ||
if (lexer.readString(3) !== 'end') { | ||
throw new Error('Invalid object: `end` not found') | ||
} | ||
return obj | ||
@@ -119,0 +124,0 @@ } |
'use strict' | ||
const util = require('../util') | ||
class PDFReference { | ||
@@ -51,3 +53,3 @@ constructor(obj) { | ||
return new PDFReference(parseObject.bind(null, xref, lexer, id)) | ||
return new PDFReference(parseObject.bind(null, xref, lexer.outer, id)) | ||
} | ||
@@ -59,2 +61,5 @@ } | ||
function parseObject(xref, lexer, id) { | ||
const PDFObject = require('./object') | ||
const Lexer = require('../parser/lexer') | ||
const obj = xref.get(id) | ||
@@ -66,6 +71,53 @@ if (obj) { | ||
const offset = xref.getOffset(id) | ||
lexer.pos = offset | ||
if (offset === null) { | ||
const entry = xref.objects[id] | ||
if (entry.compressed) { | ||
if (!entry.obj) { | ||
lexer.pos = xref.getOffset(entry.id) | ||
const obj = PDFObject.parse(xref, lexer) | ||
const PDFObject = require('./object') | ||
return PDFObject.parse(xref, lexer) | ||
const type = obj.properties.get('Type') | ||
if (type && type.name !== 'ObjStm') { | ||
throw new Error('Expected compressed object stream') | ||
} | ||
const src = util.inflate(obj) | ||
// console.log("STRING: ", String.fromCharCode.apply(null, src)) | ||
const innerLexer = new Lexer(src, lexer) | ||
obj.lexer = innerLexer | ||
obj.innerObjects = [] | ||
const n = obj.properties.get("N") | ||
for (let i = 0; i < n; ++i) { | ||
const id = innerLexer.readNumber(false) | ||
innerLexer.skipSpace(1, false) | ||
const offset = innerLexer.readNumber(false) | ||
innerLexer.skipWhitespace(1, true) | ||
obj.innerObjects.push({ | ||
id: id, | ||
offset: offset, | ||
obj: null, | ||
}) | ||
} | ||
entry.obj = obj | ||
} | ||
const inner = entry.obj.innerObjects[entry.ix] | ||
if (!inner.obj) { | ||
const innerLexer = entry.obj.lexer | ||
innerLexer.pos = entry.obj.properties.get('First') + inner.offset | ||
inner.obj = PDFObject.parseInner(xref, innerLexer) | ||
} | ||
return inner.obj | ||
} else { | ||
throw new Error('Expected compressed object stream') | ||
} | ||
} else { | ||
lexer.pos = offset | ||
return PDFObject.parse(xref, lexer) | ||
} | ||
} |
'use strict' | ||
const PDFObject = require('./object') | ||
const PDFName = require('./name') | ||
const util = require('../util') | ||
module.exports = class PDFXref { | ||
constructor() { | ||
this.objects = [] | ||
this.trailer = null | ||
} | ||
add(id, offset, obj) { | ||
this.objects[id] = { | ||
offset: offset, | ||
obj: obj, | ||
} | ||
add(id, data) { | ||
this.objects[id] = data | ||
} | ||
@@ -20,3 +22,3 @@ | ||
getOffset(id) { | ||
return this.objects[id] && this.objects[id].offset | ||
return this.objects[id] && this.objects[id].offset || null | ||
} | ||
@@ -62,9 +64,10 @@ | ||
static parse(_, lexer) { | ||
static parse(_, lexer, trial) { | ||
const xref = new PDFXref() | ||
if (lexer.readString(4) !== 'xref') { | ||
throw new Error('Invalid xref: xref expected but not found') | ||
if (lexer.getString(4) !== 'xref') { | ||
return this.parseXrefObject(_, lexer, trial) | ||
} | ||
lexer.readString(4) // skip xref | ||
lexer.skipEOL(1) | ||
@@ -90,3 +93,5 @@ | ||
if (id > 0 && key === 'n') { | ||
xref.add(id, offset, null) | ||
xref.add(id, { | ||
offset: offset, | ||
}) | ||
} | ||
@@ -99,2 +104,70 @@ } | ||
// TODO: this implementation needs to be improved | ||
static parseXrefObject(_, lexer, trial) { | ||
const xref = new PDFXref() | ||
let obj | ||
try { | ||
obj = PDFObject.parse(xref, lexer, trial) | ||
} catch (_) { | ||
throw new Error('Invalid xref: xref expected but not found') | ||
} | ||
let kind = obj.properties.get("Type") | ||
if (!kind || kind.name !== "XRef") { | ||
throw new Error("Invalid xref object at " + lexer.pos) | ||
} | ||
const stream = util.inflate(obj) | ||
xref.trailer = obj.properties | ||
const index = obj.properties.get("Index") | ||
const start = index ? index[0] : 0 | ||
const w = obj.properties.get("W") | ||
const typeSize = w[0] || 1 | ||
const offsetSize = w[1] || 2 | ||
const genSize = w[2] || 1 | ||
const len = stream.length / (typeSize + offsetSize + genSize) | ||
let pos = 0 | ||
for (let i = 0; i < len; ++i) { | ||
const type = readUint(stream, pos, typeSize) | ||
pos += typeSize | ||
const offset = readUint(stream, pos, offsetSize) | ||
pos += offsetSize | ||
switch (type) { | ||
case 0: // free | ||
pos += genSize | ||
continue // skip type 0 entries (free entries) | ||
case 1: // normal | ||
xref.add(start + i, { | ||
offset | ||
}) | ||
pos += genSize | ||
break | ||
case 2: // compressed | ||
xref.add(start + i, { | ||
compressed: true, | ||
id: offset, | ||
ix: readUint(stream, pos, genSize), | ||
}) | ||
pos += genSize | ||
break | ||
default: | ||
continue | ||
} | ||
} | ||
return xref | ||
} | ||
} | ||
function readUint(src, pos, size) { | ||
let val = 0 | ||
for (let i = 0; i < size; ++i) { | ||
// for (let i = size - 1; i > 0; --i) { | ||
val += src[pos + size - i - 1] << (8 * i) | ||
} | ||
return val | ||
} |
'use strict' | ||
class Lexer { | ||
constructor(buf) { | ||
constructor(buf, outer) { | ||
this.buf = buf | ||
this.pos = 0 | ||
this.objects = Object.create(null) | ||
this._outer = outer | ||
} | ||
get outer() { | ||
return this._outer || this | ||
} | ||
read(len) { | ||
@@ -11,0 +16,0 @@ const buf = this.buf.subarray(this.pos, this.pos + len) |
@@ -40,3 +40,3 @@ 'use strict' | ||
this.xref = PDF.Xref.parse(null, lexer) | ||
this.trailer = PDF.Trailer.parse(this.xref, lexer) | ||
this.trailer = this.xref.trailer || PDF.Trailer.parse(this.xref, lexer) | ||
@@ -55,3 +55,3 @@ let trailer = this.trailer | ||
trailer = PDF.Trailer.parse(xref, lexer) | ||
trailer = xref.trailer || PDF.Trailer.parse(xref, lexer) | ||
} | ||
@@ -58,0 +58,0 @@ } |
@@ -35,2 +35,58 @@ 'use strict' | ||
return val !== undefined ? val : def | ||
} | ||
} | ||
exports.inflate = function(obj) { | ||
let filter = obj.properties.get("Filter") | ||
if (!filter || filter.name !== "FlateDecode") { | ||
throw new Error("Only FlateDecode filter are supported") | ||
} | ||
let columns = 1 | ||
let predictor = 1 | ||
const params = obj.properties.get("DecodeParms") | ||
if (params) { | ||
columns = params.get("Columns") | ||
predictor = params.get("Predictor") | ||
} | ||
const inflate = require('pako/lib/inflate').inflate | ||
let res = inflate(obj.content.content) | ||
if (predictor === 1) { | ||
return res | ||
} | ||
if (predictor >= 10 && predictor <= 15) { | ||
// PNG filter | ||
res = pngFilter(res, columns) | ||
} else { | ||
throw new Error('Unsupported predictor ' + predictor) | ||
} | ||
return res | ||
} | ||
function pngFilter(src, columns) { | ||
const columnCount = columns + 1; | ||
const rowCount = src.length / columnCount | ||
const res = new Uint8Array(columns * rowCount); | ||
for (let y = 0; y < rowCount; ++y) { | ||
const filter = src[y * columnCount] | ||
if (filter === 0) { | ||
for (let x = 0; x < columns; ++x) { | ||
res[y * columns + x] = src[y * columnCount + 1 + x] | ||
} | ||
} else if (filter === 2) { | ||
for (let x = 0; x < columns; x++) { | ||
const prev = (y === 0) ? 0 : res[(y - 1) * columns + x] | ||
res[y * columns + x] = (prev + src[y * columnCount + 1 + x]) & 0xff | ||
} | ||
} else { | ||
throw new Error('Unsupported PNG filter ' + filter) | ||
} | ||
} | ||
return res | ||
} | ||
{ | ||
"name": "pdfjs", | ||
"author": "Markus Ast <npm.m@rkusa.st>", | ||
"version": "2.0.0-alpha.6", | ||
"version": "2.0.0-alpha.7", | ||
"description": "A Portable Document Format (PDF) generation library targeting both the server- and client-side.", | ||
@@ -15,3 +15,5 @@ "keywords": [ | ||
"scripts": { | ||
"test": "node test/index.js test/pdfs/**/*.js" | ||
"test": "npm run test:pdfs && npm run test:types", | ||
"test:pdfs": "node test/index.js", | ||
"test:types": "tsc --project ./types" | ||
}, | ||
@@ -26,3 +28,5 @@ "dependencies": { | ||
"devDependencies": { | ||
"tape": "^4.6.3" | ||
"@types/node": "^9.6.6", | ||
"tape": "^4.6.3", | ||
"typescript": "^2.8.3" | ||
}, | ||
@@ -35,3 +39,4 @@ "repository": { | ||
"node": ">=7" | ||
} | ||
}, | ||
"types": "./types/main.d.ts" | ||
} |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
321829
76
6178
3