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);
|