UNPKG

9.38 kBJavaScriptView Raw
1(function() {
2 var EventEmitter, LineBreaker, LineWrapper,
3 extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
4 hasProp = {}.hasOwnProperty;
5
6 EventEmitter = require('events').EventEmitter;
7
8 LineBreaker = require('linebreak');
9
10 LineWrapper = (function(superClass) {
11 extend(LineWrapper, superClass);
12
13 function LineWrapper(document, options) {
14 var ref;
15 this.document = document;
16 this.indent = options.indent || 0;
17 this.characterSpacing = options.characterSpacing || 0;
18 this.wordSpacing = options.wordSpacing === 0;
19 this.columns = options.columns || 1;
20 this.columnGap = (ref = options.columnGap) != null ? ref : 18;
21 this.lineWidth = (options.width - (this.columnGap * (this.columns - 1))) / this.columns;
22 this.spaceLeft = this.lineWidth;
23 this.startX = this.document.x;
24 this.startY = this.document.y;
25 this.column = 1;
26 this.ellipsis = options.ellipsis;
27 this.continuedX = 0;
28 this.features = options.features;
29 if (options.height != null) {
30 this.height = options.height;
31 this.maxY = this.startY + options.height;
32 } else {
33 this.maxY = this.document.page.maxY();
34 }
35 this.on('firstLine', (function(_this) {
36 return function(options) {
37 var indent;
38 indent = _this.continuedX || _this.indent;
39 _this.document.x += indent;
40 _this.lineWidth -= indent;
41 return _this.once('line', function() {
42 _this.document.x -= indent;
43 _this.lineWidth += indent;
44 if (options.continued && !_this.continuedX) {
45 _this.continuedX = _this.indent;
46 }
47 if (!options.continued) {
48 return _this.continuedX = 0;
49 }
50 });
51 };
52 })(this));
53 this.on('lastLine', (function(_this) {
54 return function(options) {
55 var align;
56 align = options.align;
57 if (align === 'justify') {
58 options.align = 'left';
59 }
60 _this.lastLine = true;
61 return _this.once('line', function() {
62 _this.document.y += options.paragraphGap || 0;
63 options.align = align;
64 return _this.lastLine = false;
65 });
66 };
67 })(this));
68 }
69
70 LineWrapper.prototype.wordWidth = function(word) {
71 return this.document.widthOfString(word, this) + this.characterSpacing + this.wordSpacing;
72 };
73
74 LineWrapper.prototype.eachWord = function(text, fn) {
75 var bk, breaker, fbk, l, last, lbk, mightGrow, mustShrink, shouldContinue, w, word, wordWidths;
76 breaker = new LineBreaker(text);
77 last = null;
78 wordWidths = Object.create(null);
79 while (bk = breaker.nextBreak()) {
80 word = text.slice((last != null ? last.position : void 0) || 0, bk.position);
81 w = wordWidths[word] != null ? wordWidths[word] : wordWidths[word] = this.wordWidth(word);
82 if (w > this.lineWidth + this.continuedX) {
83 lbk = last;
84 fbk = {};
85 while (word.length) {
86 if (w > this.spaceLeft) {
87 l = Math.ceil(this.spaceLeft / (w / word.length));
88 w = this.wordWidth(word.slice(0, l));
89 mightGrow = w <= this.spaceLeft && l < word.length;
90 } else {
91 l = word.length;
92 }
93 mustShrink = w > this.spaceLeft && l > 0;
94 while (mustShrink || mightGrow) {
95 if (mustShrink) {
96 w = this.wordWidth(word.slice(0, --l));
97 mustShrink = w > this.spaceLeft && l > 0;
98 } else {
99 w = this.wordWidth(word.slice(0, ++l));
100 mustShrink = w > this.spaceLeft && l > 0;
101 mightGrow = w <= this.spaceLeft && l < word.length;
102 }
103 }
104 fbk.required = bk.required || l < word.length;
105 shouldContinue = fn(word.slice(0, l), w, fbk, lbk);
106 lbk = {
107 required: false
108 };
109 word = word.slice(l);
110 w = this.wordWidth(word);
111 if (shouldContinue === false) {
112 break;
113 }
114 }
115 } else {
116 shouldContinue = fn(word, w, bk, last);
117 }
118 if (shouldContinue === false) {
119 break;
120 }
121 last = bk;
122 }
123 };
124
125 LineWrapper.prototype.wrap = function(text, options) {
126 var buffer, emitLine, lc, nextY, textWidth, wc, y;
127 if (options.indent != null) {
128 this.indent = options.indent;
129 }
130 if (options.characterSpacing != null) {
131 this.characterSpacing = options.characterSpacing;
132 }
133 if (options.wordSpacing != null) {
134 this.wordSpacing = options.wordSpacing;
135 }
136 if (options.ellipsis != null) {
137 this.ellipsis = options.ellipsis;
138 }
139 nextY = this.document.y + this.document.currentLineHeight(true);
140 if (this.document.y > this.maxY || nextY > this.maxY) {
141 this.nextSection();
142 }
143 buffer = '';
144 textWidth = 0;
145 wc = 0;
146 lc = 0;
147 y = this.document.y;
148 emitLine = (function(_this) {
149 return function() {
150 options.textWidth = textWidth + _this.wordSpacing * (wc - 1);
151 options.wordCount = wc;
152 options.lineWidth = _this.lineWidth;
153 y = _this.document.y;
154 _this.emit('line', buffer, options, _this);
155 return lc++;
156 };
157 })(this);
158 this.emit('sectionStart', options, this);
159 this.eachWord(text, (function(_this) {
160 return function(word, w, bk, last) {
161 var lh, shouldContinue;
162 if ((last == null) || last.required) {
163 _this.emit('firstLine', options, _this);
164 _this.spaceLeft = _this.lineWidth;
165 }
166 if (w <= _this.spaceLeft) {
167 buffer += word;
168 textWidth += w;
169 wc++;
170 }
171 if (bk.required || w > _this.spaceLeft) {
172 lh = _this.document.currentLineHeight(true);
173 if ((_this.height != null) && _this.ellipsis && _this.document.y + lh * 2 > _this.maxY && _this.column >= _this.columns) {
174 if (_this.ellipsis === true) {
175 _this.ellipsis = '…';
176 }
177 buffer = buffer.replace(/\s+$/, '');
178 textWidth = _this.wordWidth(buffer + _this.ellipsis);
179 while (buffer && textWidth > _this.lineWidth) {
180 buffer = buffer.slice(0, -1).replace(/\s+$/, '');
181 textWidth = _this.wordWidth(buffer + _this.ellipsis);
182 }
183 if (textWidth <= _this.lineWidth) {
184 buffer = buffer + _this.ellipsis;
185 }
186 textWidth = _this.wordWidth(buffer);
187 }
188 if (bk.required) {
189 if (w > _this.spaceLeft) {
190 emitLine();
191 buffer = word;
192 textWidth = w;
193 wc = 1;
194 }
195 _this.emit('lastLine', options, _this);
196 }
197 emitLine();
198 if (_this.document.y + lh > _this.maxY) {
199 shouldContinue = _this.nextSection();
200 if (!shouldContinue) {
201 wc = 0;
202 buffer = '';
203 return false;
204 }
205 }
206 if (bk.required) {
207 _this.spaceLeft = _this.lineWidth;
208 buffer = '';
209 textWidth = 0;
210 return wc = 0;
211 } else {
212 _this.spaceLeft = _this.lineWidth - w;
213 buffer = word;
214 textWidth = w;
215 return wc = 1;
216 }
217 } else {
218 return _this.spaceLeft -= w;
219 }
220 };
221 })(this));
222 if (wc > 0) {
223 this.emit('lastLine', options, this);
224 emitLine();
225 }
226 this.emit('sectionEnd', options, this);
227 if (options.continued === true) {
228 if (lc > 1) {
229 this.continuedX = 0;
230 }
231 this.continuedX += options.textWidth || 0;
232 return this.document.y = y;
233 } else {
234 return this.document.x = this.startX;
235 }
236 };
237
238 LineWrapper.prototype.nextSection = function(options) {
239 var ref;
240 this.emit('sectionEnd', options, this);
241 if (++this.column > this.columns) {
242 if (this.height != null) {
243 return false;
244 }
245 this.document.addPage();
246 this.column = 1;
247 this.startY = this.document.page.margins.top;
248 this.maxY = this.document.page.maxY();
249 this.document.x = this.startX;
250 if (this.document._fillColor) {
251 (ref = this.document).fillColor.apply(ref, this.document._fillColor);
252 }
253 this.emit('pageBreak', options, this);
254 } else {
255 this.document.x += this.lineWidth + this.columnGap;
256 this.document.y = this.startY;
257 this.emit('columnBreak', options, this);
258 }
259 this.emit('sectionStart', options, this);
260 return true;
261 };
262
263 return LineWrapper;
264
265 })(EventEmitter);
266
267 module.exports = LineWrapper;
268
269}).call(this);