test3 complete

main
Matthew Butterick 8 years ago
parent b56b6665c1
commit 40730a9f77

@ -266,6 +266,10 @@ module.exports =
addSegment = (cur) =>
if last < cur
hex = encoded.slice(last, cur).join ''
console.log(last)
console.log(cur)
console.log(encoded.slice(last, cur))
console.log(hex)
advance = positions[cur - 1].xAdvance - positions[cur - 1].advanceWidth
commands.push "<#{hex}> #{number(-advance)}"

@ -1,338 +0,0 @@
// Generated by CoffeeScript 1.12.5
(function() {
var LineWrapper, number;
LineWrapper = require('../line_wrapper');
number = require('../object').number;
module.exports = {
initText: function() {
this.x = 0;
this.y = 0;
return this._lineGap = 0;
},
lineGap: function(_lineGap) {
this._lineGap = _lineGap;
return this;
},
moveDown: function(lines) {
if (lines == null) {
lines = 1;
}
this.y += this.currentLineHeight(true) * lines + this._lineGap;
return this;
},
moveUp: function(lines) {
if (lines == null) {
lines = 1;
}
this.y -= this.currentLineHeight(true) * lines + this._lineGap;
return this;
},
_text: function(text, x, y, options, lineCallback) {
var j, len, line, ref, wrapper;
options = this._initOptions(x, y, options);
text = '' + text;
if (options.wordSpacing) {
text = text.replace(/\s{2,}/g, ' ');
}
if (options.width) {
wrapper = this._wrapper;
if (!wrapper) {
wrapper = new LineWrapper(this, options);
wrapper.on('line', lineCallback);
}
this._wrapper = options.continued ? wrapper : null;
this._textOptions = options.continued ? options : null;
wrapper.wrap(text, options);
} else {
ref = text.split('\n');
for (j = 0, len = ref.length; j < len; j++) {
line = ref[j];
lineCallback(line, options);
}
}
return this;
},
text: function(text, x, y, options) {
return this._text(text, x, y, options, this._line.bind(this));
},
widthOfString: function(string, options) {
if (options == null) {
options = {};
}
return this._font.widthOfString(string, this._fontSize, options.features) + (options.characterSpacing || 0) * (string.length - 1);
},
heightOfString: function(text, options) {
var height, lineGap, ref, x, y;
if (options == null) {
options = {};
}
ref = this, x = ref.x, y = ref.y;
options = this._initOptions(options);
options.height = 2e308;
lineGap = options.lineGap || this._lineGap || 0;
this._text(text, this.x, this.y, options, (function(_this) {
return function(line, options) {
return _this.y += _this.currentLineHeight(true) + lineGap;
};
})(this));
height = this.y - y;
this.x = x;
this.y = y;
return height;
},
list: function(list, x, y, options, wrapper) {
var flatten, i, indent, itemIndent, items, level, levels, midLine, r;
options = this._initOptions(x, y, options);
midLine = Math.round((this._font.ascender / 1000 * this._fontSize) / 2);
r = options.bulletRadius || Math.round((this._font.ascender / 1000 * this._fontSize) / 3);
indent = options.textIndent || r * 5;
itemIndent = options.bulletIndent || r * 8;
level = 1;
items = [];
levels = [];
flatten = function(list) {
var i, item, j, len, results;
results = [];
for (i = j = 0, len = list.length; j < len; i = ++j) {
item = list[i];
if (Array.isArray(item)) {
level++;
flatten(item);
results.push(level--);
} else {
items.push(item);
results.push(levels.push(level));
}
}
return results;
};
flatten(list);
wrapper = new LineWrapper(this, options);
wrapper.on('line', this._line.bind(this));
level = 1;
i = 0;
wrapper.on('firstLine', (function(_this) {
return function() {
var diff, l;
if ((l = levels[i++]) !== level) {
diff = itemIndent * (l - level);
_this.x += diff;
wrapper.lineWidth -= diff;
level = l;
}
_this.circle(_this.x - indent + r, _this.y + midLine, r);
return _this.fill();
};
})(this));
wrapper.on('sectionStart', (function(_this) {
return function() {
var pos;
pos = indent + itemIndent * (level - 1);
_this.x += pos;
return wrapper.lineWidth -= pos;
};
})(this));
wrapper.on('sectionEnd', (function(_this) {
return function() {
var pos;
pos = indent + itemIndent * (level - 1);
_this.x -= pos;
return wrapper.lineWidth += pos;
};
})(this));
wrapper.wrap(items.join('\n'), options);
return this;
},
_initOptions: function(x, y, options) {
var key, margins, ref, val;
if (x == null) {
x = {};
}
if (options == null) {
options = {};
}
if (typeof x === 'object') {
options = x;
x = null;
}
options = (function() {
var k, opts, v;
opts = {};
for (k in options) {
v = options[k];
opts[k] = v;
}
return opts;
})();
if (this._textOptions) {
ref = this._textOptions;
for (key in ref) {
val = ref[key];
if (key !== 'continued') {
if (options[key] == null) {
options[key] = val;
}
}
}
}
if (x != null) {
this.x = x;
}
if (y != null) {
this.y = y;
}
if (options.lineBreak !== false) {
margins = this.page.margins;
if (options.width == null) {
options.width = this.page.width - this.x - margins.right;
}
}
options.columns || (options.columns = 0);
if (options.columnGap == null) {
options.columnGap = 18;
}
return options;
},
_line: function(text, options, wrapper) {
var lineGap;
if (options == null) {
options = {};
}
this._fragment(text, this.x, this.y, options);
lineGap = options.lineGap || this._lineGap || 0;
if (!wrapper) {
return this.x += this.widthOfString(text);
} else {
return this.y += this.currentLineHeight(true) + lineGap;
}
},
_fragment: function(text, x, y, options) {
var addSegment, align, base, characterSpacing, commands, d, encoded, encodedWord, flush, hadOffset, i, j, last, len, len1, lineWidth, lineY, m, mode, name, pos, positions, positionsWord, ref, ref1, renderedWidth, scale, spaceWidth, textWidth, word, wordSpacing, words;
text = ('' + text).replace(/\n/g, '');
if (text.length === 0) {
return;
}
align = options.align || 'left';
wordSpacing = options.wordSpacing || 0;
characterSpacing = options.characterSpacing || 0;
if (options.width) {
switch (align) {
case 'right':
textWidth = this.widthOfString(text.replace(/\s+$/, ''), options);
x += options.lineWidth - textWidth;
break;
case 'center':
x += options.lineWidth / 2 - options.textWidth / 2;
break;
case 'justify':
words = text.trim().split(/\s+/);
textWidth = this.widthOfString(text.replace(/\s+/g, ''), options);
spaceWidth = this.widthOfString(' ') + characterSpacing;
wordSpacing = Math.max(0, (options.lineWidth - textWidth) / Math.max(1, words.length - 1) - spaceWidth);
}
}
renderedWidth = options.textWidth + (wordSpacing * (options.wordCount - 1)) + (characterSpacing * (text.length - 1));
if (options.link) {
this.link(x, y, renderedWidth, this.currentLineHeight(), options.link);
}
if (options.underline || options.strike) {
this.save();
if (!options.stroke) {
this.strokeColor.apply(this, this._fillColor);
}
lineWidth = this._fontSize < 10 ? 0.5 : Math.floor(this._fontSize / 10);
this.lineWidth(lineWidth);
d = options.underline ? 1 : 2;
lineY = y + this.currentLineHeight() / d;
if (options.underline) {
lineY -= lineWidth;
}
this.moveTo(x, lineY);
this.lineTo(x + renderedWidth, lineY);
this.stroke();
this.restore();
}
this.save();
this.transform(1, 0, 0, -1, 0, this.page.height);
y = this.page.height - y - (this._font.ascender / 1000 * this._fontSize);
if ((base = this.page.fonts)[name = this._font.id] == null) {
base[name] = this._font.ref();
}
this.addContent("BT");
this.addContent("1 0 0 1 " + (number(x)) + " " + (number(y)) + " Tm");
this.addContent("/" + this._font.id + " " + (number(this._fontSize)) + " Tf");
mode = options.fill && options.stroke ? 2 : options.stroke ? 1 : 0;
if (mode) {
this.addContent(mode + " Tr");
}
if (characterSpacing) {
this.addContent((number(characterSpacing)) + " Tc");
}
if (wordSpacing) {
words = text.trim().split(/\s+/);
wordSpacing += this.widthOfString(' ') + characterSpacing;
wordSpacing *= 1000 / this._fontSize;
encoded = [];
positions = [];
for (j = 0, len = words.length; j < len; j++) {
word = words[j];
ref = this._font.encode(word, options.features), encodedWord = ref[0], positionsWord = ref[1];
encoded.push.apply(encoded, encodedWord);
positions.push.apply(positions, positionsWord);
positions[positions.length - 1].xAdvance += wordSpacing;
}
} else {
ref1 = this._font.encode(text, options.features), encoded = ref1[0], positions = ref1[1];
}
scale = this._fontSize / 1000;
commands = [];
last = 0;
hadOffset = false;
addSegment = (function(_this) {
return function(cur) {
var advance, hex;
if (last < cur) {
hex = encoded.slice(last, cur).join('');
advance = positions[cur - 1].xAdvance - positions[cur - 1].advanceWidth;
commands.push("<" + hex + "> " + (number(-advance)));
}
return last = cur;
};
})(this);
flush = (function(_this) {
return function(i) {
addSegment(i);
if (commands.length > 0) {
_this.addContent("[" + (commands.join(' ')) + "] TJ");
return commands.length = 0;
}
};
})(this);
for (i = m = 0, len1 = positions.length; m < len1; i = ++m) {
pos = positions[i];
if (pos.xOffset || pos.yOffset) {
flush(i);
this.addContent("1 0 0 1 " + (number(x + pos.xOffset * scale)) + " " + (number(y + pos.yOffset * scale)) + " Tm");
flush(i + 1);
hadOffset = true;
} else {
if (hadOffset) {
this.addContent("1 0 0 1 " + (number(x)) + " " + (number(y)) + " Tm");
hadOffset = false;
}
if (pos.xAdvance - pos.advanceWidth !== 0) {
addSegment(i + 1);
}
}
x += pos.xAdvance * scale;
}
flush(i);
this.addContent("ET");
return this.restore();
}
};
}).call(this);

@ -1,7 +1,7 @@
#lang racket
(module+ test
(require #;pitfall/test/test0
#;pitfall/test/test1
#;pitfall/test/test2
(require pitfall/test/test0
pitfall/test/test1
pitfall/test/test2
pitfall/test/test3
#;pitfall/page-test))
pitfall/page-test))

@ -43,7 +43,8 @@
[bbox (· font bbox)]
[lineGap (· font lineGap)])
(as-methods
embed)))
embed
encode)))
(define/contract (embed this)
(->m void?)
@ -55,6 +56,21 @@
(· this dictionary end))
(define/contract (encode this text [options #f])
((string?) ((or/c hash? #f)) . ->*m . (list/c (listof string?) (listof hash?)))
(define encoded (send (· this font) encodeText text))
(define glyphs (send (· this font) glyphsForString (~a text)))
(define advances (send (· this font) advancesForGlyphs glyphs))
(define positions
(for/list ([(glyph i) (in-indexed glyphs)])
(mhash 'xAdvance (list-ref advances i)
'yAdvance 0
'xOffset 0
'yOffset 0
'advanceWidth (send (· this font) widthOfGlyph glyph))))
(list encoded positions))
(module+ test
(define stdfont (make-object StandardFont #f "Helvetica" #f))

@ -22,7 +22,13 @@
(- ascender descender))])
(as-methods
parse)
parse
encodeText
glyphsForString
characterToGlyph
advancesForGlyphs
widthOfGlyph
getKernPair)
))
(define/contract (AFMFont-open filename)
@ -57,7 +63,7 @@
(define assocs (for/list ([field (in-list (string-split line #px"\\s*;\\s*"))])
(string-split field " ")))
(define name (second (assoc "N" assocs)))
(define width (second (assoc "WX" assocs)))
(define width (string->number (second (assoc "WX" assocs))))
(hash-set! (· this glyphWidths) name width))]
[("KernPairs")
(when (string-prefix? line "KPX")
@ -67,78 +73,147 @@
)
(define WIN_ANSI_MAP
(hash 402 131
8211 150
8212 151
8216 145
8217 146
8218 130
8220 147
8221 148
8222 132
8224 134
8225 135
8226 149
8230 133
8364 128
8240 137
8249 139
8250 155
710 136
8482 153
338 140
339 156
732 152
352 138
353 154
376 159
381 142
382 158))
(define/contract (encodeText this text)
(string? . ->m . (listof string?))
(for/list ([c (in-string text)])
(define cint (char->integer c))
(number->string (hash-ref WIN_ANSI_MAP cint cint) 16)))
(define/contract (glyphsForString this string)
(string? . ->m . (listof any/c))
(for/list ([c (in-string string)])
(define charCode (char->integer c))
(send this characterToGlyph charCode)))
(define/contract (characterToGlyph this character)
(integer? . ->m . any)
(define idx (hash-ref WIN_ANSI_MAP character character))
(if (< idx (vector-length characters))
(vector-ref characters idx)
".notdef"))
(define/contract (widthOfGlyph this glyph)
(string? . ->m . number?)
(hash-ref (· this glyphWidths) glyph 0))
(define/contract (getKernPair this left right)
((or/c char? string?) (or/c char? string?) . ->m . number?)
(hash-ref (· this kernPairs) (format "~a~a~a" left #\nul right) 0))
(define/contract (advancesForGlyphs this glyphs)
((listof any/c) . ->m . (listof number?))
(for/list ([left (in-list glyphs)]
[right (in-list (append (cdr glyphs) (list #\nul)))])
(+ (send this widthOfGlyph left)
(send this getKernPair left right))))
(define characters (list->vector (string-split @string-append{
.notdef .notdef .notdef .notdef
.notdef .notdef .notdef .notdef
.notdef .notdef .notdef .notdef
.notdef .notdef .notdef .notdef
.notdef .notdef .notdef .notdef
.notdef .notdef .notdef .notdef
.notdef .notdef .notdef .notdef
.notdef .notdef .notdef .notdef
space exclam quotedbl numbersign
dollar percent ampersand quotesingle
parenleft parenright asterisk plus
comma hyphen period slash
zero one two three
four five six seven
eight nine colon semicolon
less equal greater question
at A B C
D E F G
H I J K
L M N O
P Q R S
T U V W
X Y Z bracketleft
backslash bracketright asciicircum underscore
grave a b c
d e f g
h i j k
l m n o
p q r s
t u v w
x y z braceleft
bar braceright asciitilde .notdef
Euro .notdef quotesinglbase florin
quotedblbase ellipsis dagger daggerdbl
circumflex perthousand Scaron guilsinglleft
OE .notdef Zcaron .notdef
.notdef quoteleft quoteright quotedblleft
quotedblright bullet endash emdash
tilde trademark scaron guilsinglright
oe .notdef zcaron ydieresis
space exclamdown cent sterling
currency yen brokenbar section
dieresis copyright ordfeminine guillemotleft
logicalnot hyphen registered macron
degree plusminus twosuperior threesuperior
acute mu paragraph periodcentered
cedilla onesuperior ordmasculine guillemotright
onequarter onehalf threequarters questiondown
Agrave Aacute Acircumflex Atilde
Adieresis Aring AE Ccedilla
Egrave Eacute Ecircumflex Edieresis
Igrave Iacute Icircumflex Idieresis
Eth Ntilde Ograve Oacute
Ocircumflex Otilde Odieresis multiply
Oslash Ugrave Uacute Ucircumflex
Udieresis Yacute Thorn germandbls
agrave aacute acircumflex atilde
adieresis aring ae ccedilla
egrave eacute ecircumflex edieresis
igrave iacute icircumflex idieresis
eth ntilde ograve oacute
ocircumflex otilde odieresis divide
oslash ugrave uacute ucircumflex
udieresis yacute thorn ydieresis})))
.notdef .notdef .notdef .notdef
.notdef .notdef .notdef .notdef
.notdef .notdef .notdef .notdef
.notdef .notdef .notdef .notdef
.notdef .notdef .notdef .notdef
.notdef .notdef .notdef .notdef
.notdef .notdef .notdef .notdef
.notdef .notdef .notdef .notdef
space exclam quotedbl numbersign
dollar percent ampersand quotesingle
parenleft parenright asterisk plus
comma hyphen period slash
zero one two three
four five six seven
eight nine colon semicolon
less equal greater question
at A B C
D E F G
H I J K
L M N O
P Q R S
T U V W
X Y Z bracketleft
backslash bracketright asciicircum underscore
grave a b c
d e f g
h i j k
l m n o
p q r s
t u v w
x y z braceleft
bar braceright asciitilde .notdef
Euro .notdef quotesinglbase florin
quotedblbase ellipsis dagger daggerdbl
circumflex perthousand Scaron guilsinglleft
OE .notdef Zcaron .notdef
.notdef quoteleft quoteright quotedblleft
quotedblright bullet endash emdash
tilde trademark scaron guilsinglright
oe .notdef zcaron ydieresis
space exclamdown cent sterling
currency yen brokenbar section
dieresis copyright ordfeminine guillemotleft
logicalnot hyphen registered macron
degree plusminus twosuperior threesuperior
acute mu paragraph periodcentered
cedilla onesuperior ordmasculine guillemotright
onequarter onehalf threequarters questiondown
Agrave Aacute Acircumflex Atilde
Adieresis Aring AE Ccedilla
Egrave Eacute Ecircumflex Edieresis
Igrave Iacute Icircumflex Idieresis
Eth Ntilde Ograve Oacute
Ocircumflex Otilde Odieresis multiply
Oslash Ugrave Uacute Ucircumflex
Udieresis Yacute Thorn germandbls
agrave aacute acircumflex atilde
adieresis aring ae ccedilla
egrave eacute ecircumflex edieresis
igrave iacute icircumflex idieresis
eth ntilde ograve oacute
ocircumflex otilde odieresis divide
oslash ugrave uacute ucircumflex
udieresis yacute thorn ydieresis})))
(module+ test
(define afmfont (AFMFont-open "data/helvetica.afm"))

@ -1,4 +1,5 @@
#lang pitfall/racket
(require sugar/list)
(provide text-mixin)
(define (text-mixin [% mixin-tester%])
@ -140,7 +141,8 @@
;; text position
(send this addContent (format "1 0 0 1 ~a ~a Tm" (number x) (number y)))
;; font and font size ; todo
;; font and font size
(send this addContent (format "/~a ~a Tf" (· this _font id) (number (· this _fontSize))))
;; rendering mode
(define mode (cond
@ -158,13 +160,70 @@
;; If we have a word spacing value, we need to encode each word separately
;; since the normal Tw operator only works on character code 32, which isn't
;; used for embedded fonts.
;; todo
;; Adds a segment of text to the TJ command buffer ; todo
;; Flushes the current TJ commands to the output stream ; todo
(match-define (list encoded positions)
(cond
[(not (zero? wordSpacing))
(void)] ; todo
[else
(send (· this _font) encode text (hash-ref options 'features #f))]))
(define scale (/ (· this _fontSize) 1000.0))
(define commands empty)
(define last 0)
(define hadOffset #f)
;; Adds a segment of text to the TJ command buffer
(define (addSegment cur)
(when (< last cur)
(define hex (string-append* (sublist encoded last cur)))
(define advance
(let ([pos (list-ref positions (sub1 cur))])
(- (· pos xAdvance) (· pos advanceWidth))))
(push-end! commands
(format "<~a> ~a" hex (number (- advance)))))
(set! last cur))
;; Flushes the current TJ commands to the output stream
(define (flush i)
(addSegment i)
(when (positive? (length commands))
(send this addContent (format "[~a] TJ" (string-join commands " ")))
(set! commands empty)))
;; Flush any remaining commands ; todo
(for ([(pos i) (in-indexed positions)])
;; If we have an x or y offset, we have to break out of the current TJ command
;; so we can move the text position.
(cond
[(or (not (zero? (· pos xOffset))) (not (zero? (· pos yOffset))))
;; Flush the current buffer
(flush i)
;; Move the text position and flush just the current character
(send this addContent
(format "1 0 0 1 ~a ~a Tm"
(number (+ x (* (· pos xOffset) scale)))
(number (+ y (* (· pos yOffset) scale)))))
(flush (add1 i))
(set! hadOffset #t)]
[else
;; If the last character had an offset, reset the text position
(when hadOffset
(send this addContent (format "1 0 0 1 ~a ~a Tm"
(number x) (number y)))
(set! hadOffset #f))
;; Group segments that don't have any advance adjustments
(unless (zero? (- (· pos xAdvance) (· pos advanceWidth)))
(addSegment (add1 i)))])
(set! x (+ x (* (· pos xAdvance) scale))))
;; Flush any remaining commands
(let ([i (length positions)])
(flush i))
;; end the text object
(send this addContent "ET")

@ -38,7 +38,7 @@ endobj
<<
/Producer (PDFKit)
/Creator (PDFKit)
/CreationDate (D:20170516193500Z)
/CreationDate (D:20170516210014Z)
>>
endobj
6 0 obj

@ -7,4 +7,4 @@
(send doc text "Hello world")
(send doc end)))
;(check-copy-equal? this)
(check-copy-equal? this)

@ -0,0 +1,83 @@
%PDF-1.3
%ÿÿÿÿ
5 0 obj
<<
/Parent 1 0 R
/Resources 4 0 R
/Contents 3 0 R
/MediaBox [0 0 612 792]
/Type /Page
>>
endobj
4 0 obj
<<
/Font <<
/F1 6 0 R
>>
/ProcSet [/PDF /Text /ImageB /ImageC /ImageI]
>>
endobj
3 0 obj
<<
/Length 123
>>
stream
1 0 0 -1 0 792 cm
q
1 0 0 -1 0 792 cm
BT
1 0 0 1 72 711.384 Tm
/F1 12 Tf
[<48656c6c6f2077> 10 <6f72> -15 <6c64> 0] TJ
ET
Q
endstream
endobj
7 0 obj
<<
/CreationDate (D:19700101000000Z)
/Creator (PitfallKit)
/Producer (PitfallKit)
>>
endobj
6 0 obj
<<
/BaseFont /Helvetica
/Encoding /WinAnsiEncoding
/Subtype /Type1
/Type /Font
>>
endobj
2 0 obj
<<
/Pages 1 0 R
/Type /Catalog
>>
endobj
1 0 obj
<<
/Kids [5 0 R]
/Count 1
/Type /Pages
>>
endobj
xref
0 8
0000000000 65535 f
0000000628 00000 n
0000000579 00000 n
0000000208 00000 n
0000000119 00000 n
0000000015 00000 n
0000000482 00000 n
0000000382 00000 n
trailer
<<
/Info 7 0 R
/Root 2 0 R
/Size 8
>>
startxref
685
%%EOF

@ -19,7 +19,7 @@ endobj
endobj
3 0 obj
<<
/Length 68
/Length 123
>>
stream
1 0 0 -1 0 792 cm
@ -27,6 +27,8 @@ q
1 0 0 -1 0 792 cm
BT
1 0 0 1 72 711.384 Tm
/F1 12 Tf
[<48656c6c6f2077> 10 <6f72> -15 <6c64> 0] TJ
ET
Q
@ -63,13 +65,13 @@ endobj
xref
0 8
0000000000 65535 f
0000000572 00000 n
0000000523 00000 n
0000000628 00000 n
0000000579 00000 n
0000000208 00000 n
0000000119 00000 n
0000000015 00000 n
0000000426 00000 n
0000000326 00000 n
0000000482 00000 n
0000000382 00000 n
trailer
<<
/Info 7 0 R
@ -77,5 +79,5 @@ trailer
/Size 8
>>
startxref
629
685
%%EOF

Loading…
Cancel
Save