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