util.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587
  1. /* Copyright 2020 Mozilla Foundation
  2. *
  3. * Licensed under the Apache License, Version 2.0 (the "License");
  4. * you may not use this file except in compliance with the License.
  5. * You may obtain a copy of the License at
  6. *
  7. * http://www.apache.org/licenses/LICENSE-2.0
  8. *
  9. * Unless required by applicable law or agreed to in writing, software
  10. * distributed under the License is distributed on an "AS IS" BASIS,
  11. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. * See the License for the specific language governing permissions and
  13. * limitations under the License.
  14. */
  15. import { PDFObject } from "./pdf_object.js";
  16. class Util extends PDFObject {
  17. constructor(data) {
  18. super(data);
  19. this._scandCache = new Map();
  20. this._months = [
  21. "January",
  22. "February",
  23. "March",
  24. "April",
  25. "May",
  26. "June",
  27. "July",
  28. "August",
  29. "September",
  30. "October",
  31. "November",
  32. "December",
  33. ];
  34. this._days = [
  35. "Sunday",
  36. "Monday",
  37. "Tuesday",
  38. "Wednesday",
  39. "Thursday",
  40. "Friday",
  41. "Saturday",
  42. ];
  43. this.MILLISECONDS_IN_DAY = 86400000;
  44. this.MILLISECONDS_IN_WEEK = 604800000;
  45. // used with crackURL
  46. this._externalCall = data.externalCall;
  47. }
  48. printf(...args) {
  49. if (args.length === 0) {
  50. throw new Error("Invalid number of params in printf");
  51. }
  52. if (typeof args[0] !== "string") {
  53. throw new TypeError("First argument of printf must be a string");
  54. }
  55. const pattern = /%(,[0-4])?([+ 0#]+)?(\d+)?(\.\d+)?(.)/g;
  56. const PLUS = 1;
  57. const SPACE = 2;
  58. const ZERO = 4;
  59. const HASH = 8;
  60. let i = 0;
  61. return args[0].replace(
  62. pattern,
  63. function (match, nDecSep, cFlags, nWidth, nPrecision, cConvChar) {
  64. // cConvChar must be one of d, f, s, x
  65. if (
  66. cConvChar !== "d" &&
  67. cConvChar !== "f" &&
  68. cConvChar !== "s" &&
  69. cConvChar !== "x"
  70. ) {
  71. const buf = ["%"];
  72. for (const str of [nDecSep, cFlags, nWidth, nPrecision, cConvChar]) {
  73. if (str) {
  74. buf.push(str);
  75. }
  76. }
  77. return buf.join("");
  78. }
  79. i++;
  80. if (i === args.length) {
  81. throw new Error("Not enough arguments in printf");
  82. }
  83. const arg = args[i];
  84. if (cConvChar === "s") {
  85. return arg.toString();
  86. }
  87. let flags = 0;
  88. if (cFlags) {
  89. for (const flag of cFlags) {
  90. switch (flag) {
  91. case "+":
  92. flags |= PLUS;
  93. break;
  94. case " ":
  95. flags |= SPACE;
  96. break;
  97. case "0":
  98. flags |= ZERO;
  99. break;
  100. case "#":
  101. flags |= HASH;
  102. break;
  103. }
  104. }
  105. }
  106. cFlags = flags;
  107. if (nWidth) {
  108. nWidth = parseInt(nWidth);
  109. }
  110. let intPart = Math.trunc(arg);
  111. if (cConvChar === "x") {
  112. let hex = Math.abs(intPart).toString(16).toUpperCase();
  113. if (nWidth !== undefined) {
  114. hex = hex.padStart(nWidth, cFlags & ZERO ? "0" : " ");
  115. }
  116. if (cFlags & HASH) {
  117. hex = `0x${hex}`;
  118. }
  119. return hex;
  120. }
  121. if (nPrecision) {
  122. nPrecision = parseInt(nPrecision.substring(1));
  123. }
  124. nDecSep = nDecSep ? nDecSep.substring(1) : "0";
  125. const separators = {
  126. 0: [",", "."],
  127. 1: ["", "."],
  128. 2: [".", ","],
  129. 3: ["", ","],
  130. 4: ["'", "."],
  131. };
  132. const [thousandSep, decimalSep] = separators[nDecSep];
  133. let decPart = "";
  134. if (cConvChar === "f") {
  135. if (nPrecision !== undefined) {
  136. decPart = Math.abs(arg - intPart).toFixed(nPrecision);
  137. } else {
  138. decPart = Math.abs(arg - intPart).toString();
  139. }
  140. if (decPart.length > 2) {
  141. decPart = `${decimalSep}${decPart.substring(2)}`;
  142. } else {
  143. if (decPart === "1") {
  144. intPart += Math.sign(arg);
  145. }
  146. decPart = cFlags & HASH ? "." : "";
  147. }
  148. }
  149. let sign = "";
  150. if (intPart < 0) {
  151. sign = "-";
  152. intPart = -intPart;
  153. } else if (cFlags & PLUS) {
  154. sign = "+";
  155. } else if (cFlags & SPACE) {
  156. sign = " ";
  157. }
  158. if (thousandSep && intPart >= 1000) {
  159. const buf = [];
  160. while (true) {
  161. buf.push((intPart % 1000).toString().padStart(3, "0"));
  162. intPart = Math.trunc(intPart / 1000);
  163. if (intPart < 1000) {
  164. buf.push(intPart.toString());
  165. break;
  166. }
  167. }
  168. intPart = buf.reverse().join(thousandSep);
  169. } else {
  170. intPart = intPart.toString();
  171. }
  172. let n = `${intPart}${decPart}`;
  173. if (nWidth !== undefined) {
  174. n = n.padStart(nWidth - sign.length, cFlags & ZERO ? "0" : " ");
  175. }
  176. return `${sign}${n}`;
  177. }
  178. );
  179. }
  180. iconStreamFromIcon() {
  181. /* Not implemented */
  182. }
  183. printd(cFormat, oDate) {
  184. switch (cFormat) {
  185. case 0:
  186. return this.printd("D:yyyymmddHHMMss", oDate);
  187. case 1:
  188. return this.printd("yyyy.mm.dd HH:MM:ss", oDate);
  189. case 2:
  190. return this.printd("m/d/yy h:MM:ss tt", oDate);
  191. }
  192. const handlers = {
  193. mmmm: data => {
  194. return this._months[data.month];
  195. },
  196. mmm: data => {
  197. return this._months[data.month].substring(0, 3);
  198. },
  199. mm: data => {
  200. return (data.month + 1).toString().padStart(2, "0");
  201. },
  202. m: data => {
  203. return (data.month + 1).toString();
  204. },
  205. dddd: data => {
  206. return this._days[data.dayOfWeek];
  207. },
  208. ddd: data => {
  209. return this._days[data.dayOfWeek].substring(0, 3);
  210. },
  211. dd: data => {
  212. return data.day.toString().padStart(2, "0");
  213. },
  214. d: data => {
  215. return data.day.toString();
  216. },
  217. yyyy: data => {
  218. return data.year.toString();
  219. },
  220. yy: data => {
  221. return (data.year % 100).toString().padStart(2, "0");
  222. },
  223. HH: data => {
  224. return data.hours.toString().padStart(2, "0");
  225. },
  226. H: data => {
  227. return data.hours.toString();
  228. },
  229. hh: data => {
  230. return (1 + ((data.hours + 11) % 12)).toString().padStart(2, "0");
  231. },
  232. h: data => {
  233. return (1 + ((data.hours + 11) % 12)).toString();
  234. },
  235. MM: data => {
  236. return data.minutes.toString().padStart(2, "0");
  237. },
  238. M: data => {
  239. return data.minutes.toString();
  240. },
  241. ss: data => {
  242. return data.seconds.toString().padStart(2, "0");
  243. },
  244. s: data => {
  245. return data.seconds.toString();
  246. },
  247. tt: data => {
  248. return data.hours < 12 ? "am" : "pm";
  249. },
  250. t: data => {
  251. return data.hours < 12 ? "a" : "p";
  252. },
  253. };
  254. const data = {
  255. year: oDate.getFullYear(),
  256. month: oDate.getMonth(),
  257. day: oDate.getDate(),
  258. dayOfWeek: oDate.getDay(),
  259. hours: oDate.getHours(),
  260. minutes: oDate.getMinutes(),
  261. seconds: oDate.getSeconds(),
  262. };
  263. const patterns =
  264. /(mmmm|mmm|mm|m|dddd|ddd|dd|d|yyyy|yy|HH|H|hh|h|MM|M|ss|s|tt|t|\\.)/g;
  265. return cFormat.replace(patterns, function (match, pattern) {
  266. if (pattern in handlers) {
  267. return handlers[pattern](data);
  268. }
  269. return pattern.charCodeAt(1);
  270. });
  271. }
  272. printx(cFormat, cSource) {
  273. // case
  274. cSource = (cSource ?? "").toString();
  275. const handlers = [x => x, x => x.toUpperCase(), x => x.toLowerCase()];
  276. const buf = [];
  277. let i = 0;
  278. const ii = cSource.length;
  279. let currCase = handlers[0];
  280. let escaped = false;
  281. for (const command of cFormat) {
  282. if (escaped) {
  283. buf.push(command);
  284. escaped = false;
  285. continue;
  286. }
  287. if (i >= ii) {
  288. break;
  289. }
  290. switch (command) {
  291. case "?":
  292. buf.push(currCase(cSource.charAt(i++)));
  293. break;
  294. case "X":
  295. while (i < ii) {
  296. const char = cSource.charAt(i++);
  297. if (
  298. ("a" <= char && char <= "z") ||
  299. ("A" <= char && char <= "Z") ||
  300. ("0" <= char && char <= "9")
  301. ) {
  302. buf.push(currCase(char));
  303. break;
  304. }
  305. }
  306. break;
  307. case "A":
  308. while (i < ii) {
  309. const char = cSource.charAt(i++);
  310. if (("a" <= char && char <= "z") || ("A" <= char && char <= "Z")) {
  311. buf.push(currCase(char));
  312. break;
  313. }
  314. }
  315. break;
  316. case "9":
  317. while (i < ii) {
  318. const char = cSource.charAt(i++);
  319. if ("0" <= char && char <= "9") {
  320. buf.push(char);
  321. break;
  322. }
  323. }
  324. break;
  325. case "*":
  326. while (i < ii) {
  327. buf.push(currCase(cSource.charAt(i++)));
  328. }
  329. break;
  330. case "\\":
  331. escaped = true;
  332. break;
  333. case ">":
  334. currCase = handlers[1];
  335. break;
  336. case "<":
  337. currCase = handlers[2];
  338. break;
  339. case "=":
  340. currCase = handlers[0];
  341. break;
  342. default:
  343. buf.push(command);
  344. }
  345. }
  346. return buf.join("");
  347. }
  348. scand(cFormat, cDate) {
  349. if (typeof cDate !== "string") {
  350. return new Date(cDate);
  351. }
  352. if (cDate === "") {
  353. return new Date();
  354. }
  355. switch (cFormat) {
  356. case 0:
  357. return this.scand("D:yyyymmddHHMMss", cDate);
  358. case 1:
  359. return this.scand("yyyy.mm.dd HH:MM:ss", cDate);
  360. case 2:
  361. return this.scand("m/d/yy h:MM:ss tt", cDate);
  362. }
  363. if (!this._scandCache.has(cFormat)) {
  364. const months = this._months;
  365. const days = this._days;
  366. const handlers = {
  367. mmmm: {
  368. pattern: `(${months.join("|")})`,
  369. action: (value, data) => {
  370. data.month = months.indexOf(value);
  371. },
  372. },
  373. mmm: {
  374. pattern: `(${months.map(month => month.substring(0, 3)).join("|")})`,
  375. action: (value, data) => {
  376. data.month = months.findIndex(
  377. month => month.substring(0, 3) === value
  378. );
  379. },
  380. },
  381. mm: {
  382. pattern: `(\\d{2})`,
  383. action: (value, data) => {
  384. data.month = parseInt(value) - 1;
  385. },
  386. },
  387. m: {
  388. pattern: `(\\d{1,2})`,
  389. action: (value, data) => {
  390. data.month = parseInt(value) - 1;
  391. },
  392. },
  393. dddd: {
  394. pattern: `(${days.join("|")})`,
  395. action: (value, data) => {
  396. data.day = days.indexOf(value);
  397. },
  398. },
  399. ddd: {
  400. pattern: `(${days.map(day => day.substring(0, 3)).join("|")})`,
  401. action: (value, data) => {
  402. data.day = days.findIndex(day => day.substring(0, 3) === value);
  403. },
  404. },
  405. dd: {
  406. pattern: "(\\d{2})",
  407. action: (value, data) => {
  408. data.day = parseInt(value);
  409. },
  410. },
  411. d: {
  412. pattern: "(\\d{1,2})",
  413. action: (value, data) => {
  414. data.day = parseInt(value);
  415. },
  416. },
  417. yyyy: {
  418. pattern: "(\\d{4})",
  419. action: (value, data) => {
  420. data.year = parseInt(value);
  421. },
  422. },
  423. yy: {
  424. pattern: "(\\d{2})",
  425. action: (value, data) => {
  426. data.year = 2000 + parseInt(value);
  427. },
  428. },
  429. HH: {
  430. pattern: "(\\d{2})",
  431. action: (value, data) => {
  432. data.hours = parseInt(value);
  433. },
  434. },
  435. H: {
  436. pattern: "(\\d{1,2})",
  437. action: (value, data) => {
  438. data.hours = parseInt(value);
  439. },
  440. },
  441. hh: {
  442. pattern: "(\\d{2})",
  443. action: (value, data) => {
  444. data.hours = parseInt(value);
  445. },
  446. },
  447. h: {
  448. pattern: "(\\d{1,2})",
  449. action: (value, data) => {
  450. data.hours = parseInt(value);
  451. },
  452. },
  453. MM: {
  454. pattern: "(\\d{2})",
  455. action: (value, data) => {
  456. data.minutes = parseInt(value);
  457. },
  458. },
  459. M: {
  460. pattern: "(\\d{1,2})",
  461. action: (value, data) => {
  462. data.minutes = parseInt(value);
  463. },
  464. },
  465. ss: {
  466. pattern: "(\\d{2})",
  467. action: (value, data) => {
  468. data.seconds = parseInt(value);
  469. },
  470. },
  471. s: {
  472. pattern: "(\\d{1,2})",
  473. action: (value, data) => {
  474. data.seconds = parseInt(value);
  475. },
  476. },
  477. tt: {
  478. pattern: "([aApP][mM])",
  479. action: (value, data) => {
  480. const char = value.charAt(0);
  481. data.am = char === "a" || char === "A";
  482. },
  483. },
  484. t: {
  485. pattern: "([aApP])",
  486. action: (value, data) => {
  487. data.am = value === "a" || value === "A";
  488. },
  489. },
  490. };
  491. // escape the string
  492. const escapedFormat = cFormat.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&");
  493. const patterns =
  494. /(mmmm|mmm|mm|m|dddd|ddd|dd|d|yyyy|yy|HH|H|hh|h|MM|M|ss|s|tt|t)/g;
  495. const actions = [];
  496. const re = escapedFormat.replace(
  497. patterns,
  498. function (match, patternElement) {
  499. const { pattern, action } = handlers[patternElement];
  500. actions.push(action);
  501. return pattern;
  502. }
  503. );
  504. this._scandCache.set(cFormat, [re, actions]);
  505. }
  506. const [re, actions] = this._scandCache.get(cFormat);
  507. const matches = new RegExp(`^${re}$`, "g").exec(cDate);
  508. if (!matches || matches.length !== actions.length + 1) {
  509. return null;
  510. }
  511. const data = {
  512. year: 2000,
  513. month: 0,
  514. day: 1,
  515. hours: 0,
  516. minutes: 0,
  517. seconds: 0,
  518. am: null,
  519. };
  520. actions.forEach((action, i) => action(matches[i + 1], data));
  521. if (data.am !== null) {
  522. data.hours = (data.hours % 12) + (data.am ? 0 : 12);
  523. }
  524. return new Date(
  525. data.year,
  526. data.month,
  527. data.day,
  528. data.hours,
  529. data.minutes,
  530. data.seconds
  531. );
  532. }
  533. spansToXML() {
  534. /* Not implemented */
  535. }
  536. stringFromStream() {
  537. /* Not implemented */
  538. }
  539. xmlToSpans() {
  540. /* Not implemented */
  541. }
  542. }
  543. export { Util };