]> git.kianting.info Git - clo/blob - src/libclo/index.js
add grid
[clo] / src / libclo / index.js
1 "use strict";
2 var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3 if (k2 === undefined) k2 = k;
4 var desc = Object.getOwnPropertyDescriptor(m, k);
5 if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6 desc = { enumerable: true, get: function() { return m[k]; } };
7 }
8 Object.defineProperty(o, k2, desc);
9 }) : (function(o, m, k, k2) {
10 if (k2 === undefined) k2 = k;
11 o[k2] = m[k];
12 }));
13 var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14 Object.defineProperty(o, "default", { enumerable: true, value: v });
15 }) : function(o, v) {
16 o["default"] = v;
17 });
18 var __importStar = (this && this.__importStar) || function (mod) {
19 if (mod && mod.__esModule) return mod;
20 var result = {};
21 if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22 __setModuleDefault(result, mod);
23 return result;
24 };
25 var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
26 function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
27 return new (P || (P = Promise))(function (resolve, reject) {
28 function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
29 function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
30 function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
31 step((generator = generator.apply(thisArg, _arguments || [])).next());
32 });
33 };
34 Object.defineProperty(exports, "__esModule", { value: true });
35 exports.Clo = exports.calculateTextWidthHeightAux = exports.calculateTextWidthHeight = exports.hyphenTkTree = exports.filterEmptyString = exports.spacesToBreakpoint = exports.hyphenForClo = exports.splitCJKV = exports.twoReturnsToNewline = exports.ptToPx = exports.cjkvRegexPattern = exports.cjkvBlocksInRegex = exports.defaultFrameStyle = exports.defaultTextStyle = exports.A4_IN_PX = exports.Direction = void 0;
36 const canva_1 = require("../canva");
37 const fontkit = __importStar(require("fontkit"));
38 const breakLines = __importStar(require("./breakLines"));
39 require("pdfkit");
40 const pdf_lib_1 = require("pdf-lib");
41 const fs = __importStar(require("fs"));
42 /**
43 * TYPES
44 */
45 /**
46 * text direction
47 * LTR - left to right
48 * TTB - top to bottom
49 * etc.
50 */
51 var Direction;
52 (function (Direction) {
53 Direction[Direction["LTR"] = 0] = "LTR";
54 Direction[Direction["RTL"] = 1] = "RTL";
55 Direction[Direction["TTB"] = 2] = "TTB";
56 Direction[Direction["BTT"] = 3] = "BTT";
57 })(Direction || (exports.Direction = Direction = {}));
58 /**
59 * DEFAULT CONST PART
60 */
61 exports.A4_IN_PX = { "width": 793.7,
62 "height": 1122.5 };
63 exports.defaultTextStyle = {
64 family: "FreeSerif",
65 size: ptToPx(12),
66 textWeight: canva_1.TextWeight.REGULAR,
67 fontStyle: canva_1.FontStyle.ITALIC,
68 };
69 exports.defaultFrameStyle = {
70 directionInsideLine: Direction.LTR,
71 direction: Direction.TTB,
72 baseLineskip: ptToPx(15),
73 textStyle: exports.defaultTextStyle,
74 x: exports.A4_IN_PX.width * 0.10,
75 y: exports.A4_IN_PX.height * 0.10,
76 width: exports.A4_IN_PX.width * 0.80,
77 height: exports.A4_IN_PX.height * 0.80,
78 content: null,
79 };
80 /**
81 * definition for cjk scripts
82 * - Hani : Han Character
83 * - Hang : Hangul
84 * - Bopo : Bopomofo
85 * - Kana : Katakana
86 * - Hira : Hiragana
87 */
88 exports.cjkvBlocksInRegex = ["Hani", "Hang", "Bopo", "Kana", "Hira"];
89 exports.cjkvRegexPattern = new RegExp("((?:" +
90 exports.cjkvBlocksInRegex.map((x) => "\\p{Script_Extensions=" + x + "}").join("|") + ")+)", "gu");
91 /**
92 * FUNCTION PART
93 */
94 /**
95 * convert from ptToPx
96 * @param pt pt size value
97 * @returns the corresponding px value
98 */
99 function ptToPx(pt) {
100 return pt * 4.0 / 3.0;
101 }
102 exports.ptToPx = ptToPx;
103 /**
104 * REGISTER PART
105 */
106 /**
107 * convert '\n\n' to newline command ["nl"]
108 * @param arr the input `tkTree`
109 * @param clo the `Clo` object
110 * @returns the input tktree
111 */
112 function twoReturnsToNewline(arr, clo) {
113 var middle = [];
114 for (let i = 0; i < arr.length; i++) {
115 var item = arr[i];
116 if (!Array.isArray(item)) {
117 middle = middle.concat(item.split(/(\n\n)/g));
118 }
119 else {
120 middle.push(item);
121 }
122 }
123 var result = [];
124 for (let j = 0; j < middle.length; j++) {
125 var item = middle[j];
126 if (!Array.isArray(item) && item == "\n\n") {
127 result.push(["nl"]); // push a newline command to the result `tkTree`
128 }
129 else {
130 result.push(middle[j]);
131 }
132 }
133 return result;
134 }
135 exports.twoReturnsToNewline = twoReturnsToNewline;
136 /**
137 * split CJKV and non-CJKV
138 *
139 * @param arr : input tkTree
140 * @returns a splitted tkTree (by CJK and NonCJK)
141 * - Examples:
142 * ```
143 * [`many臺中daylight`] => [`many`, `臺中`, `dahylight`]
144 * ```
145 */
146 function splitCJKV(arr, clo) {
147 var result = [];
148 for (let i = 0; i < arr.length; i++) {
149 var item = arr[i];
150 if (!Array.isArray(item)) {
151 result = result.concat(item.split(exports.cjkvRegexPattern));
152 }
153 else {
154 result.push(item);
155 }
156 }
157 return result;
158 }
159 exports.splitCJKV = splitCJKV;
160 /**
161 * hyphenation for a clo document
162 * @param arr the array for a `tkTree`
163 * @param clo the Clo object
164 */
165 function hyphenForClo(arr, clo) {
166 let hyphenLanguage = clo.attrs["hyphenLanguage"];
167 let res = hyphenTkTree(arr, hyphenLanguage);
168 return res;
169 }
170 exports.hyphenForClo = hyphenForClo;
171 /**
172 * convert spaces to Breakpoint
173 * \s+ => ["bp" [\s+] ""]
174 * @param arr the tkTree input text stream
175 * @param clo the Clo object
176 * @returns the converted object
177 */
178 function spacesToBreakpoint(arr, clo) {
179 let spacePattern = /^([ \t]+)$/g;
180 var result = [];
181 for (let i = 0; i < arr.length; i++) {
182 var item = arr[i];
183 if (!Array.isArray(item) && item.match(spacePattern)) {
184 // push a breakpoint command to the result `tkTree`
185 result.push(['bp', [["hglue", "0.1"], item], ""]);
186 }
187 else {
188 result.push(item);
189 }
190 }
191 return result;
192 }
193 exports.spacesToBreakpoint = spacesToBreakpoint;
194 /**
195 * remove all the `` (empty string) in the arr
196 * @param arr the tkTree to be filtered
197 * @param clo the Clo file
198 */
199 function filterEmptyString(arr, clo) {
200 if (Array.isArray(arr)) {
201 arr.filter((x) => { return x != ``; });
202 }
203 return arr;
204 }
205 exports.filterEmptyString = filterEmptyString;
206 /**
207 * OTHER FUNCTIONS
208 */
209 /**
210 * hyphenate for a tkTree
211 * - hyphenation => ["bp", "", "-"]
212 * @param arr the tkTree array
213 * @param lang ISO 639 code for the language
214 */
215 function hyphenTkTree(arr, lang) {
216 // import corresponding hyphen language data and function
217 let hyphen = require("hyphen/" + lang);
218 let result = [];
219 for (let i = 0; i < arr.length; i++) {
220 let element = arr[i];
221 let splitter = "分"; // a CJKV
222 if (!Array.isArray(element)) {
223 let hyphenatedElement = hyphen.hyphenateSync(element, { hyphenChar: splitter });
224 let hyphenatedSplitted = hyphenatedElement.split(splitter);
225 var newSplitted = [];
226 for (var j = 0; j < hyphenatedSplitted.length - 1; j++) {
227 newSplitted.push(hyphenatedSplitted[j]);
228 // "bp" for breakpoint
229 newSplitted.push(["bp", "", "-"]); //insert a breakable point (bp) mark
230 }
231 newSplitted.push(hyphenatedSplitted[hyphenatedSplitted.length - 1]);
232 result = result.concat(newSplitted);
233 }
234 else {
235 result.push(element);
236 }
237 }
238 return result;
239 }
240 exports.hyphenTkTree = hyphenTkTree;
241 /**
242 * calculate the text width and Height with a given `TextStyle`
243 * @param preprocessed
244 * @param defaultFontStyle
245 */
246 function calculateTextWidthHeight(element, style) {
247 return __awaiter(this, void 0, void 0, function* () {
248 var res = [];
249 for (var i = 0; i < element.length; i++) {
250 res.push(yield calculateTextWidthHeightAux(element[i], style));
251 }
252 res = res.flat();
253 return res;
254 });
255 }
256 exports.calculateTextWidthHeight = calculateTextWidthHeight;
257 /**
258 * calculate the text width and Height with a given `TextStyle`
259 * @param preprocessed
260 * @param defaultFontStyle
261 */
262 function calculateTextWidthHeightAux(element, style) {
263 return __awaiter(this, void 0, void 0, function* () {
264 var result = [];
265 let fontPair = (0, canva_1.fontStyleTofont)(style);
266 if (fontPair.path.match(/\.ttc$/)) {
267 var font = yield fontkit.openSync(fontPair.path, fontPair.psName);
268 }
269 else {
270 var font = yield fontkit.openSync(fontPair.path);
271 }
272 if (!Array.isArray(element)) {
273 var run = font.layout(element, undefined, undefined, undefined, "ltr");
274 for (var j = 0; j < run.glyphs.length; j++) {
275 let runGlyphsItem = run.glyphs[j];
276 let item = {
277 x: null,
278 y: null,
279 textStyle: style,
280 direction: Direction.LTR,
281 width: (runGlyphsItem.advanceWidth) * (style.size) / 1000,
282 height: (runGlyphsItem.bbox.maxY - runGlyphsItem.bbox.minY) * (style.size) / 1000,
283 content: element[j],
284 minX: runGlyphsItem.bbox.minX,
285 maxX: runGlyphsItem.bbox.maxX,
286 minY: runGlyphsItem.bbox.minY,
287 maxY: runGlyphsItem.bbox.maxY
288 };
289 result.push(item);
290 }
291 return result;
292 }
293 else if (element[0] == "bp") {
294 var beforeNewLine = yield calculateTextWidthHeightAux(element[1], style);
295 if (Array.isArray(beforeNewLine)) {
296 beforeNewLine = beforeNewLine.flat();
297 }
298 let afterNewLine = yield calculateTextWidthHeightAux(element[2], style);
299 if (Array.isArray(afterNewLine)) {
300 afterNewLine = afterNewLine.flat();
301 }
302 let breakPointNode = {
303 original: beforeNewLine,
304 newLined: afterNewLine,
305 };
306 return breakPointNode;
307 }
308 else if (element[0] == "hglue" && !Array.isArray(element[1])) {
309 let hGlue = { stretchFactor: parseFloat(element[1]) };
310 return hGlue;
311 }
312 else {
313 return calculateTextWidthHeight(element, style);
314 }
315 });
316 }
317 exports.calculateTextWidthHeightAux = calculateTextWidthHeightAux;
318 /**
319 * whole document-representing class
320 */
321 class Clo {
322 constructor() {
323 this.preprocessors = [];
324 this.mainStream = [];
325 this.attrs = {
326 "page": exports.A4_IN_PX,
327 "defaultFrameStyle": exports.defaultFrameStyle,
328 "hyphenLanguage": 'en' // hyphenated in the language (in ISO 639)
329 };
330 // register the precessor functions
331 this.preprocessorRegister(splitCJKV);
332 this.preprocessorRegister(hyphenForClo);
333 this.preprocessorRegister(twoReturnsToNewline);
334 this.preprocessorRegister(spacesToBreakpoint);
335 this.preprocessorRegister(filterEmptyString);
336 }
337 setAttr(attr, val) {
338 Object.assign(this.attrs, attr, val);
339 }
340 getAttr(attr) {
341 if (Object.keys(this.attrs).length === 0) {
342 return this.attrs[attr];
343 }
344 else {
345 return undefined;
346 }
347 }
348 /**
349 * register a function of preprocessor
350 * @param f a function
351 */
352 preprocessorRegister(f) {
353 this.preprocessors.push(f);
354 }
355 generatePdf() {
356 return __awaiter(this, void 0, void 0, function* () {
357 // preprocessed
358 var preprocessed = this.mainStream;
359 for (var i = 0; i < this.preprocessors.length; i++) {
360 preprocessed = this.preprocessors[i](preprocessed, this);
361 }
362 // generate the width and height of the stream
363 let defaultFontStyle = this.attrs["defaultFrameStyle"].textStyle;
364 let a = yield calculateTextWidthHeight(preprocessed, defaultFontStyle);
365 let breakLineAlgorithms = new breakLines.BreakLineAlgorithm();
366 // TODO
367 //console.log(breakLineAlgorithms.totalCost(a,70));
368 let segmentedNodes = breakLineAlgorithms.segmentedNodes(a, 70);
369 console.log(this.segmentedNodesToFrameBox(segmentedNodes, this.attrs["defaultFrameStyle"]));
370 // generate pdf
371 const pdfDoc = yield pdf_lib_1.PDFDocument.create();
372 var page = pdfDoc.addPage();
373 page.drawText('You can create PDFs!');
374 for (var j = 0; j < 1000; j += 5) {
375 if (j % 50 == 0) {
376 page.drawText(i.toString(), { x: 50, y: j });
377 }
378 page.drawLine({
379 start: { x: 0, y: j },
380 end: { x: 1000, y: j },
381 thickness: 0.5,
382 color: (0, pdf_lib_1.rgb)(0.75, 0.2, 0.2),
383 opacity: 0.20,
384 });
385 }
386 for (var i = 0; i < 1000; i += 5) {
387 if (i % 50 == 0) {
388 page.drawText(i.toString(), { x: i, y: 50 });
389 }
390 page.drawLine({
391 start: { x: i, y: 0 },
392 end: { x: i, y: 1000 },
393 thickness: 0.5,
394 color: (0, pdf_lib_1.rgb)(0.75, 0.2, 0.2),
395 opacity: 0.20,
396 });
397 }
398 pdfDoc.save();
399 const pdfBytes = yield pdfDoc.save();
400 fs.writeFileSync("blank.pdf", pdfBytes);
401 });
402 }
403 segmentedNodesToFrameBox(segmentedNodes, frame) {
404 let baseLineskip = frame.baseLineskip;
405 let boxArrayEmpty = [];
406 let bigBox = {
407 x: frame.x,
408 y: frame.y,
409 textStyle: frame.textStyle,
410 direction: frame.direction,
411 width: frame.width,
412 height: frame.height,
413 content: boxArrayEmpty,
414 };
415 var bigBoxContent = boxArrayEmpty;
416 let segmentedNodesFixed = segmentedNodes.map((x) => this.removeBreakPoints(x).flat());
417 let segmentedNodeUnglue = segmentedNodesFixed.map((x) => this.removeGlue(x, frame).flat());
418 for (var i = 0; i < segmentedNodesFixed.length - 1; i++) {
419 var currentLineSkip = baseLineskip;
420 var glyphMaxHeight = this.getGlyphMaxHeight(segmentedNodesFixed[i]);
421 if (currentLineSkip === null || glyphMaxHeight > currentLineSkip) {
422 currentLineSkip = glyphMaxHeight;
423 }
424 var currentLineBox = {
425 x: null,
426 y: null,
427 textStyle: exports.defaultTextStyle,
428 direction: frame.directionInsideLine,
429 width: frame.width,
430 height: currentLineSkip,
431 content: segmentedNodeUnglue[i],
432 };
433 bigBoxContent.push(currentLineBox);
434 }
435 bigBox.content = bigBoxContent;
436 return bigBox;
437 }
438 /**
439 * get the max height of the glyph`[a, b, c]`
440 * @param nodeLine the node line [a, b, c, ...]
441 * @returns
442 */
443 getGlyphMaxHeight(nodeLine) {
444 let segmentedNodeLineHeight = nodeLine.map((x) => { if ("height" in x && x.height > 0.0) {
445 return x.height;
446 }
447 else {
448 return 0.0;
449 } });
450 let maxHeight = Math.max(...segmentedNodeLineHeight);
451 return maxHeight;
452 }
453 removeGlue(nodeLine, frame) {
454 let breakLineAlgorithms = new breakLines.BreakLineAlgorithm();
455 let glueRemoved = nodeLine.filter((x) => !breakLineAlgorithms.isHGlue(x));
456 let onlyGlue = nodeLine.filter((x) => breakLineAlgorithms.isHGlue(x));
457 let sumStretchFactor = onlyGlue.map((x) => { if ("stretchFactor" in x) {
458 return x.stretchFactor;
459 }
460 else {
461 return 0;
462 } })
463 .reduce((acc, cur) => acc + cur, 0);
464 let glueRemovedWidth = glueRemoved.map((x) => { if ("width" in x) {
465 return x.width;
466 }
467 else {
468 return 0;
469 } })
470 .reduce((acc, cur) => acc + cur, 0);
471 let offset = frame.width - glueRemovedWidth;
472 var res = [];
473 for (var i = 0; i < nodeLine.length; i++) {
474 var ele = nodeLine[i];
475 if (breakLineAlgorithms.isHGlue(ele)) {
476 let tmp = {
477 x: null,
478 y: null,
479 textStyle: null,
480 direction: frame.directionInsideLine,
481 width: ele.stretchFactor / sumStretchFactor * offset,
482 height: 0,
483 content: "",
484 };
485 res.push(tmp);
486 }
487 else {
488 res.push(ele);
489 }
490 }
491 return res;
492 }
493 /**
494 * remove breakpoints
495 * @param boxitemline boxitem in a line with a breakpoint
496 * @returns boxitemline with break points removed
497 */
498 removeBreakPoints(boxitemline) {
499 var res = [];
500 let breakLineAlgorithms = new breakLines.BreakLineAlgorithm();
501 for (var i = 0; i < boxitemline.length; i++) {
502 let ele = boxitemline[i];
503 if (breakLineAlgorithms.isBreakPoint(ele)) {
504 if (i == boxitemline.length - 1) {
505 res.push(ele.newLined);
506 }
507 else {
508 res.push(ele.original);
509 }
510 }
511 else {
512 res.push(ele);
513 }
514 }
515 return res;
516 }
517 }
518 exports.Clo = Clo;
519 /*
520 export let a = new Clo();
521 export default a; */