Ingen beskrivning
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

card.ts 31KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824
  1. module RpgCards {
  2. function normalizeTag(tag: string): string {
  3. return tag.trim().toLowerCase();
  4. }
  5. function splitParams(value: string): string[] {
  6. return value.split("|").map(function (str) { return str.trim(); });
  7. }
  8. export class Options {
  9. foreground_color: string;
  10. background_color: string;
  11. empty_color: string;
  12. default_color: string;
  13. default_icon: string;
  14. default_title_size: string;
  15. page_size: string;
  16. page_rows: number;
  17. page_columns: number;
  18. card_arrangement: string;
  19. card_size: string;
  20. card_count: number;
  21. icon_inline: boolean;
  22. constructor() {
  23. this.foreground_color = "white";
  24. this.background_color = "white";
  25. this.empty_color = "black";
  26. this.default_color = "black";
  27. this.default_icon = "";
  28. this.default_title_size = "13";
  29. this.page_size = "A4";
  30. this.page_rows = 3;
  31. this.page_columns = 3;
  32. this.card_arrangement = "doublesided";
  33. this.card_size = "25x35";
  34. this.card_count = null;
  35. this.icon_inline = true;
  36. }
  37. }
  38. export class Card {
  39. count: number;
  40. title: string;
  41. title_size: string;
  42. title_icon_text: string;
  43. color: string;
  44. color_front: string;
  45. color_back: string;
  46. icon: string;
  47. icon_front: string;
  48. icon_back: string;
  49. contents: string[];
  50. tags: string[];
  51. userData: any;
  52. constructor() {
  53. this.count = 1;
  54. this.title = "New card";
  55. this.title_size = null;
  56. this.title_icon_text = null;
  57. this.color = null;
  58. this.color_front = null;
  59. this.color_back = null;
  60. this.icon = null;
  61. this.icon_front = null;
  62. this.icon_back = null;
  63. this.contents = [];
  64. this.tags = [];
  65. this.userData = null;
  66. }
  67. static fromJSON(json: any): Card {
  68. var result = new Card;
  69. result.count = json.count || 1;
  70. result.title = json.title || "";
  71. result.title_size = json.title_size || null;
  72. result.title_icon_text = json.title_icon_text || null;
  73. result.color = json.color || null;
  74. result.color_front = json.color_front || null;
  75. result.color_back = json.color_back || null;
  76. result.icon = json.icon || null;
  77. result.icon_front = json.icon_front || null;
  78. result.icon_back = json.icon_back || null;
  79. result.contents = json.contents || [];
  80. result.tags = json.tags || [];
  81. return result;
  82. }
  83. public toJSON(): any {
  84. return {
  85. count: this.count,
  86. title: this.title,
  87. title_size: this.title_size,
  88. title_icon_text: this.title_icon_text,
  89. color: this.color,
  90. color_front: this.color_front,
  91. color_back: this.color_back,
  92. icon: this.icon,
  93. icon_front: this.icon_front,
  94. icon_back: this.icon_back,
  95. contents: this.contents.slice(),
  96. tags: this.tags.slice()
  97. }
  98. }
  99. public duplicate(): Card {
  100. var result = Card.fromJSON(this.toJSON());
  101. result.title += " (Copy)";
  102. return result;
  103. }
  104. public hasTag(tag: string): boolean {
  105. var index = this.tags.indexOf(normalizeTag(tag));
  106. return index > -1;
  107. }
  108. public addTag(tag: string): void {
  109. if (!this.hasTag(tag)) {
  110. this.tags.push(normalizeTag(tag));
  111. }
  112. }
  113. public removeTag(tag: string): void {
  114. var ntag = normalizeTag(tag);
  115. this.tags = this.tags.filter(function (t) {
  116. return ntag != t;
  117. });
  118. }
  119. public findTag(pattern: string, flags?: string): string[]{
  120. var f = flags || "g";
  121. var regexp = new RegExp(pattern, f);
  122. var s = this.tags.join("\n");
  123. return regexp.exec(s);
  124. }
  125. public replaceTag(pattern: string, substitution: string, flags?: string): void {
  126. var f = flags || "g";
  127. var regexp = new RegExp(pattern, f);
  128. this.tags = this.tags.map((s) => s.replace(regexp, substitution));
  129. }
  130. public findContent(pattern: string, flags?: string): string[] {
  131. var f = flags || "g";
  132. var regexp = new RegExp(pattern, f);
  133. var s = this.contents.join("\n");
  134. return regexp.exec(s);
  135. }
  136. public replaceContent(pattern: string, substitution: string, flags?: string): void {
  137. var f = flags || "g";
  138. var regexp = new RegExp(pattern, f);
  139. this.contents = this.contents.map((s) => s.replace(regexp, substitution));
  140. }
  141. public findTitle(pattern: string, flags?: string): string[] {
  142. var f = flags || "g";
  143. var regexp = new RegExp(pattern, f);
  144. var s = this.title;
  145. return regexp.exec(s);
  146. }
  147. public findTitleAny(patterns: string[], flags?: string): string[] {
  148. for (var i = 0; i < patterns.length; ++i) {
  149. var f = flags || "g";
  150. var regexp = new RegExp(patterns[i], f);
  151. var s = this.title;
  152. var result = regexp.exec(s);
  153. if (result) {
  154. return result;
  155. }
  156. }
  157. return null;
  158. }
  159. public getTitle(options: Options): string {
  160. return this.title || "";
  161. }
  162. public getTitleSize(options: Options): string {
  163. return this.title_size || options.default_title_size || "13";
  164. }
  165. public getTitleIconText(options: Options): string {
  166. return this.title_icon_text || "";
  167. }
  168. public getColorFront(options: Options): string {
  169. return this.color_front || this.color || options.default_color || "black";
  170. }
  171. public getColorBack(options: Options): string {
  172. return this.color_back || this.color || options.default_color || "black";
  173. }
  174. public getIconFront(options: Options): string {
  175. return this.icon_front || this.icon || options.default_icon || "";
  176. }
  177. public getIconBack(options: Options): string {
  178. return this.icon_back || this.icon || options.default_icon || "";
  179. }
  180. };
  181. interface CardAction {
  182. fn: string;
  183. card: Card;
  184. ref: Card;
  185. }
  186. export class CardDeck {
  187. cards: Card[];
  188. private _actions: CardAction[];
  189. constructor() {
  190. this.cards = [];
  191. this._actions = [];
  192. }
  193. public toJSON(): any {
  194. return this.cards.map((card) => card.toJSON());
  195. }
  196. public static fromJSON(data: any): CardDeck {
  197. if (Array.isArray(data)) {
  198. var result = new CardDeck;
  199. for (var i = 0; i < data.length; ++i) {
  200. result.cards.push(Card.fromJSON(data[i]));
  201. }
  202. return result;
  203. } else {
  204. throw new Error("Invalid data");
  205. }
  206. }
  207. public addCards(cards: Card[]): void {
  208. cards.forEach((card) => {
  209. this._actions.push({fn:"add", card: card, ref: null});
  210. });
  211. }
  212. public addNewCard(): Card {
  213. var newCard = new Card();
  214. this._actions.push({ fn: "add", card: newCard, ref: null });
  215. return newCard;
  216. }
  217. public duplicateCard(card: Card): Card {
  218. var newCard = card.duplicate();
  219. this._actions.push({ fn: "add", card: newCard, ref: card });
  220. return newCard;
  221. }
  222. public deleteCard(card: Card): void {
  223. this._actions.push({ fn: "del", card: card, ref: null });
  224. }
  225. public commit() {
  226. for (var i = 0; i < this._actions.length; ++i) {
  227. var action = this._actions[i];
  228. if (action.fn === "add") {
  229. var index = this.cards.indexOf(action.ref);
  230. if (index > -1) {
  231. this.cards.splice(index + 1, 0, action.card);
  232. } else {
  233. this.cards.push(action.card);
  234. }
  235. } else if (action.fn === "del") {
  236. var index = this.cards.indexOf(action.card);
  237. if (index > -1) {
  238. this.cards.splice(index, 1);
  239. }
  240. }
  241. }
  242. this._actions = [];
  243. }
  244. }
  245. type ContentGeneratorFunction = (params: string[], card: Card, options: Options, ind: string, ind0: string) => string;
  246. type CardGeneratorFunction = (card: Card, options: Options, ind: string, ind0: string) => string;
  247. export class CardHtmlGenerator {
  248. constructor() {
  249. }
  250. private _icon(src: string, ind: string, ind0: string): string {
  251. if (src.length > 0) {
  252. return ind + '<card-icon src="./icons/' + src + '.svg"></card-icon>\n';
  253. } else {
  254. return "";
  255. }
  256. }
  257. private _subtitle(params: string[], card: Card, options: Options, ind: string, ind0: string): string {
  258. var text = params[0] || "";
  259. return ind + '<card-subtitle>' + text + '</card-subtitle>\n';
  260. }
  261. private _ruler(params: string[], card: Card, options: Options, ind: string, ind0: string): string {
  262. return ind + '<card-rule></card-rule>\n';
  263. }
  264. private _boxes(params: string[], card: Card, options: Options, ind: string, ind0: string): string {
  265. var count = params[0] || 1;
  266. var size = params[1] || 3;
  267. return ind + '<card-boxes size="' + size + '" count="' + count + '"></card-boxes>\n';
  268. }
  269. private _property(params: string[], card: Card, options: Options, ind: string, ind0: string): string {
  270. var header = params[0] || "";
  271. var text = params[1] || "";
  272. var result = "";
  273. result += ind + '<card-property>\n';
  274. result += ind + ind0 + '<h4>' + header + '</h4>\n';
  275. result += ind + ind0 + '<p>' + text + '</p>\n';
  276. result += ind + '</card-property>\n';
  277. return result;
  278. }
  279. private _description(params: string[], card: Card, options: Options, ind: string, ind0: string): string {
  280. var header = params[0] || "";
  281. var text = params[1] || "";
  282. var result = "";
  283. result += ind + '<card-description>\n';
  284. result += ind + ind0 + '<h4>' + header + '</h4>\n';
  285. result += ind + ind0 + '<p>' + text + '</p>\n';
  286. result += ind + '</card-description>\n';
  287. return result;
  288. }
  289. private _text(params: string[], card: Card, options: Options, ind: string, ind0: string): string {
  290. var text = params[0] || "";
  291. var result = "";
  292. result += ind + '<card-description>\n';
  293. result += ind + ind0 + '<p>' + text + '</p>\n';
  294. result += ind + '</card-description>\n';
  295. return result;
  296. }
  297. private _dndstats(params: string[], card: Card, options: Options, ind: string, ind0: string): string {
  298. var stats = ["str", "dex", "con", "int", "wis", "cha"];
  299. var result = "";
  300. result += ind + '<card-dndstats';
  301. for (var i = 0; i < stats.length; ++i) {
  302. var value = params[i] || "";
  303. var stat = stats[i];
  304. result += ' ' + stat + '="' + value + '"';
  305. }
  306. result += '></card-dndstats>\n';
  307. return result;
  308. }
  309. private _bullet(params: string[], card: Card, options: Options, ind: string, ind0: string): string {
  310. var text = params[0] || "";
  311. return ind + '<card-bullet>' + text + '</card-bullet>\n';
  312. }
  313. private _section(params: string[], card: Card, options: Options, ind: string, ind0: string): string {
  314. var text = params[0] || "";
  315. return ind + '<card-section>' + text + '</card-section>\n';
  316. }
  317. private _fill(params: string[], card: Card, options: Options, ind: string, ind0: string): string {
  318. var size = params[0] || "1";
  319. return ind + '<card-fill size="' + size + '"></card-fill>\n';
  320. }
  321. private _vspace(params: string[], card: Card, options: Options, ind: string, ind0: string): string {
  322. var size = params[0] || "1em";
  323. return ind + '<card-vspace size="' + size + '"></card-vspace>\n';
  324. }
  325. private _unknown(params: string[], card: Card, options: Options, ind: string, ind0: string): string {
  326. var text = params.join(' | ');
  327. return ind + '<card-description><p>' + text + '</p></card-description>\n';
  328. }
  329. private _empty(params: string[], card: Card, options: Options, ind: string, ind0: string) {
  330. return '';
  331. }
  332. private _contents(contents: string[], card: Card, options: Options, ind: string, ind0: string): string {
  333. var result = "";
  334. result += ind + '<card-contents>\n';
  335. result += contents.map((value) => {
  336. var parts = splitParams(value);
  337. var name = parts[0];
  338. var params = parts.splice(1);
  339. var generator: ContentGeneratorFunction = null;
  340. switch (name) {
  341. case "subtitle": generator = this._subtitle; break;
  342. case "property": generator = this._property; break;
  343. case "rule": generator = this._ruler; break;
  344. case "ruler": generator = this._ruler; break;
  345. case "boxes": generator = this._boxes; break;
  346. case "description": generator = this._description; break;
  347. case "dndstats": generator = this._dndstats; break;
  348. case "text": generator = this._text; break;
  349. case "bullet": generator = this._bullet; break;
  350. case "fill": generator = this._fill; break;
  351. case "vspace": generator = this._vspace; break;
  352. case "section": generator = this._section; break;
  353. case "disabled": generator = this._empty; break;
  354. case "": generator = this._empty; break;
  355. default: return this._unknown(parts, card, options, ind, ind0);
  356. }
  357. return generator(params, card, options, ind + ind0, ind);
  358. }).join("\n");
  359. result += ind + '</card-contents>\n';
  360. return result;
  361. }
  362. private _title(card: Card, options: Options, ind: string, ind0: string): string {
  363. var title = card.getTitle(options);
  364. var title_size = card.getTitleSize(options);
  365. var title_icon_text = card.getTitleIconText(options);
  366. var icon = card.getIconFront(options);
  367. var result = "";
  368. result += ind + '<card-title size="' + title_size + '">\n';
  369. result += ind + ind0 + '<h1>' + title + '</h1>\n';
  370. result += ind + ind0 + '<h2>' + title_icon_text + '</h2>\n';
  371. result += this._icon(icon, ind + ind0, ind0);
  372. result += ind + '</card-title>\n';
  373. return result;
  374. }
  375. private _card_front(card: Card, options: Options, ind: string, ind0: string): string {
  376. var result = "";
  377. result += this._title(card, options, ind + ind0, ind0);
  378. result += this._contents(card.contents, card, options, ind + ind0, ind0);
  379. return result;
  380. }
  381. private _card_back(card: Card, options: Options, ind: string, ind0: string): string {
  382. var icon = card.getIconBack(options);
  383. var result = "";
  384. result += ind + '<card-back>\n';
  385. result += this._icon(icon, ind + ind0, ind);
  386. result += ind + '</card-back>\n';
  387. return result;
  388. }
  389. private _card_empty(options: Options, ind: string, ind0: string): string {
  390. var result = "";
  391. result += ind + '<card-contents>\n';
  392. result += ind + '</card-contents>\n';
  393. return result;
  394. }
  395. private _card(options: Options, ind: string, ind0: string, content: string, color: string): string {
  396. var size = options.card_size || "25x35";
  397. var result = "";
  398. result += ind + '<rpg-card color="' + color + '" size="' + size + '">\n';
  399. result += content;
  400. result += ind + '</rpg-card>\n';
  401. return result;
  402. }
  403. /** Generates HTML for the front side of the given card */
  404. public card_front(card: Card, options: Options, indent: string): string {
  405. var content = this._card_front(card, options, "", indent);
  406. return this._card(options, "", indent, content, card.getColorFront(options));
  407. }
  408. /** Generates HTML for the back side of the given card */
  409. public card_back(card: Card, options: Options, indent: string): string {
  410. var content = this._card_back(card, options, "", indent);
  411. return this._card(options, "", indent, content, card.getColorBack(options));
  412. }
  413. /** Generates HTML for an empty given card */
  414. public card_empty(options: Options, indent: string): string {
  415. var content = this._card_empty(options, "", indent);
  416. return this._card(options, "", indent, content, options.empty_color);
  417. }
  418. }
  419. class CardPage<T> {
  420. rows: number;
  421. cols: number;
  422. cards: T[];
  423. constructor(rows: number, cols: number) {
  424. this.rows = rows;
  425. this.cols = cols;
  426. this.cards = [];
  427. }
  428. /** Returns an empty page with the same dimensions */
  429. public newPage(): CardPage<T> {
  430. return new CardPage<T>(this.rows, this.cols);
  431. }
  432. private _posToIndex(row: number, col: number): number {
  433. return row * this.cols + col;
  434. }
  435. /** Adds one card to the page */
  436. public addCard(card: T): void {
  437. if (this.capacity() === 0) {
  438. throw new Error("This page is full.");
  439. }
  440. this.cards.push(card);
  441. }
  442. /**
  443. Adds several copies of a card to the page.
  444. Returns the number of copies that did not fit on the page.
  445. */
  446. public addCards(card: T, count: number): number {
  447. while (this.capacity() > 0 && count > 0) {
  448. this.addCard(card);
  449. --count;
  450. }
  451. return count;
  452. }
  453. /** Fills all remaining slots on the current row with the given card */
  454. public fillRow(card: T): void {
  455. while (this.capacityRow() > 0) {
  456. this.addCard(card);
  457. }
  458. }
  459. /** Fills all remaining slots on the page with empty cards */
  460. public fillPage(card: T): void {
  461. while (this.capacity() > 0) {
  462. this.addCard(card);
  463. }
  464. }
  465. /** Empty slots on the page */
  466. public capacity(): number {
  467. return this.rows * this.cols - this.cards.length;
  468. }
  469. /** Empty slots on the current line */
  470. public capacityRow(): number {
  471. return this.capacity() % this.cols;
  472. }
  473. /** Flip card slots horizontally */
  474. public flipH() {
  475. if (this.capacity() > 0) {
  476. throw new Error("Cannot perform this operation while the page is not full");
  477. }
  478. for (var r = 0; r < this.rows; ++r) {
  479. for (var c = 0; c < Math.floor(this.cols / 2); ++c) {
  480. var indexL = this._posToIndex(r, c);
  481. var indexR = this._posToIndex(r, this.cols - c - 1);
  482. var cardL = this.cards[indexL];
  483. var cardR = this.cards[indexR];
  484. this.cards[indexL] = cardR;
  485. this.cards[indexR] = cardL;
  486. }
  487. }
  488. }
  489. }
  490. class CardPageSet<T> {
  491. rows: number;
  492. cols: number;
  493. pages: CardPage<T>[];
  494. constructor(rows: number, cols: number) {
  495. this.rows = rows;
  496. this.cols = cols;
  497. this.pages = [];
  498. }
  499. public lastPage(): CardPage<T> {
  500. if (this.pages.length === 0) {
  501. return null;
  502. } else {
  503. return this.pages[this.pages.length - 1];
  504. }
  505. }
  506. public addPage(): CardPage<T> {
  507. var newPage = new CardPage<T>(this.rows, this.cols);
  508. this.pages.push(newPage);
  509. return newPage;
  510. }
  511. /**
  512. Adds one card to the last page.
  513. Adds a new pages if necessary.
  514. */
  515. public addCard(card: T): void {
  516. var page = this.lastPage();
  517. if (page === null || page.capacity() === 0) {
  518. page = this.addPage();
  519. }
  520. page.addCard(card);
  521. }
  522. /**
  523. Adds several copies of a card to the last page.
  524. Adds new pages if necessary.
  525. */
  526. public addCards(card: T, count: number): void {
  527. for (var i = 0; i < count; ++i) {
  528. this.addCard(card);
  529. }
  530. }
  531. public forEach(fn: (page: CardPage<T>)=>void) {
  532. this.pages.forEach(fn);
  533. }
  534. public merge(other: CardPageSet<T>): CardPageSet<T> {
  535. if (this.pages.length !== other.pages.length) {
  536. throw new Error("This function is only for merging two equally sized page sets");
  537. }
  538. var result = new CardPageSet<T>(this.rows, this.cols);
  539. for (var i = 0; i < this.pages.length; ++i) {
  540. result.pages.push(this.pages[i]);
  541. result.pages.push(other.pages[i]);
  542. }
  543. return result;
  544. }
  545. }
  546. class BoxSize {
  547. page: string;
  548. width: string;
  549. height: string;
  550. width_px: number;
  551. height_px: number;
  552. constructor(p: string, w: string, h: string, wpx: number, hpx: number) {
  553. this.page = p;
  554. this.width = w;
  555. this.height = h;
  556. this.width_px = Math.floor(wpx);
  557. this.height_px = Math.floor(hpx);
  558. }
  559. }
  560. var boxSizes: { [id: string]: BoxSize } = {};
  561. boxSizes["auto"] = new BoxSize("auto", "auto", "auto", Infinity, Infinity);
  562. boxSizes["A2"] = new BoxSize("A2 portrait", "420mm", "594mm", 1587.401575, 2245.03937);
  563. boxSizes["A3"] = new BoxSize("A3 portrait", "297mm", "420mm", 1118.740158, 1587.401575);
  564. boxSizes["A4"] = new BoxSize("A4 portrait", "210mm", "297mm", 793.700787, 1118.740158);
  565. boxSizes["A5"] = new BoxSize("A5 portrait", "148mm", "210mm", 559.370079, 793.700787);
  566. boxSizes["Letter"] = new BoxSize("Letter portrait", "8.5in", "11in", 816, 1056);
  567. boxSizes["225x35"] = new BoxSize("2.25in 3.5in", "2.25in", "3.5in", 216, 336);
  568. boxSizes["25x35"] = new BoxSize("2.5in 3.5in", "2.5in", "3.5in", 240, 336);
  569. boxSizes["35x50"] = new BoxSize("3.5in 5.0in", "3.5in", "5.0in", 336, 480);
  570. boxSizes["50x70"] = new BoxSize("5.0in 7.0in", "5.0in", "7.0in", 480, 672);
  571. export class PageHtmlGenerator {
  572. indent: string;
  573. constructor() {
  574. this.indent = " ";
  575. }
  576. private _pageColor(page: number, options: Options): string {
  577. if ((options.card_arrangement == "doublesided") && (page % 2 == 1)) {
  578. return options.background_color;
  579. } else {
  580. return options.foreground_color;
  581. }
  582. }
  583. private _wrap(pageSet: CardPageSet<string>, options: Options) {
  584. var result = "";
  585. for (var i = 0; i < pageSet.pages.length; ++i) {
  586. var page = pageSet.pages[i];
  587. var style = ' style="background-color:' + this._pageColor(i, options) + '"';
  588. result += '<card-page' + style + '>\n';
  589. result += page.cards.join("");
  590. result += '</card-page>\n';
  591. }
  592. return result;
  593. }
  594. private _generatePagesDoublesided(cards: Card[], options: Options, rows: number, cols: number, generator: CardHtmlGenerator): CardPageSet<string> {
  595. var front_pages: CardPageSet<string> = new CardPageSet<string>(rows, cols);
  596. var back_pages: CardPageSet<string> = new CardPageSet<string>(rows, cols);
  597. var empty = generator.card_empty(options, this.indent);
  598. // Fill pages with cards
  599. for (var i = 0; i < cards.length; ++i) {
  600. var card = cards[i];
  601. var front = generator.card_front(card, options, this.indent);
  602. var back = generator.card_back(card, options, this.indent);
  603. front_pages.addCards(front, card.count);
  604. back_pages.addCards(back, card.count);
  605. }
  606. // Fill empty slots
  607. front_pages.forEach((page) => page.fillPage(empty));
  608. back_pages.forEach((page) => page.fillPage(empty));
  609. // Shuffle back cards so that they line up with their corresponding front cards
  610. back_pages.forEach((page) => page.flipH());
  611. // Interleave front and back pages so that we can print double-sided
  612. return front_pages.merge(back_pages);
  613. }
  614. private _generatePagesFrontOnly(cards: Card[], options: Options, rows: number, cols: number, generator: CardHtmlGenerator): CardPageSet<string> {
  615. var pages: CardPageSet<string> = new CardPageSet<string>(rows, cols);
  616. var empty = generator.card_empty(options, this.indent);
  617. // Fill pages with cards
  618. for (var i = 0; i < cards.length; ++i) {
  619. var card = cards[i];
  620. var front = generator.card_front(card, options, this.indent);
  621. pages.addCards(front, card.count);
  622. }
  623. // Fill empty slots
  624. pages.forEach((page) => page.fillPage(empty));
  625. return pages;
  626. }
  627. private _generatePagesSideBySide(cards: Card[], options: Options, rows: number, cols: number, generator: CardHtmlGenerator): CardPageSet<string> {
  628. if (cols < 2) {
  629. throw new Error("Need at least two columns for side-by-side");
  630. }
  631. var pages: CardPageSet<string> = new CardPageSet<string>(rows, cols);
  632. var empty = generator.card_empty(options, this.indent);
  633. // Fill pages with cards (two at a time)
  634. for (var i = 0; i < cards.length; ++i) {
  635. var card = cards[i];
  636. var front = generator.card_front(card, options, this.indent);
  637. var back = generator.card_back(card, options, this.indent);
  638. for (var j = 0; j < card.count; ++j) {
  639. if (pages.pages.length > 0 && pages.lastPage().capacityRow() < 2) {
  640. pages.lastPage().fillRow(empty);
  641. }
  642. pages.addCard(front);
  643. pages.addCard(back);
  644. }
  645. }
  646. // Fill empty slots
  647. pages.forEach((page) => page.fillPage(empty));
  648. return pages;
  649. }
  650. private _generatePages(cards: Card[], options: Options, rows: number, cols: number, generator: CardHtmlGenerator): CardPageSet<string> {
  651. switch (options.card_arrangement) {
  652. case "doublesided": return this._generatePagesDoublesided(cards, options, rows, cols, generator);
  653. case "front_only": return this._generatePagesFrontOnly(cards, options, rows, cols, generator);
  654. case "side_by_side": return this._generatePagesSideBySide(cards, options, rows, cols, generator);
  655. default: throw new Error("Unknown card arrangement");
  656. }
  657. }
  658. private _generateStyle(options) {
  659. var page_box = boxSizes[options.page_size] || boxSizes["auto"];
  660. var result = '';
  661. result += '<style type="text/css">\n';
  662. result += 'body {\n';
  663. result += ' display: block;\n';
  664. result += ' background: rgb(204, 204, 204);\n';
  665. result += '}\n';
  666. result += 'card-page {\n';
  667. result += ' width: ' + (page_box.width_px) + 'px;\n';
  668. result += ' height: ' + (page_box.height_px) + 'px;\n';
  669. result += '}\n';
  670. result += '@media print {\n';
  671. result += ' html, body {\n';
  672. result += ' width: ' + page_box.width_px + 'px;\n';
  673. result += ' }\n';
  674. result += ' body {\n';
  675. result += ' margin: 0;\n';
  676. result += ' padding: 0;\n';
  677. result += ' border: 0;\n';
  678. result += ' }\n';
  679. result += '}\n';
  680. result += '@page {\n';
  681. result += ' margin: 0;\n';
  682. result += ' size:' + page_box.width_px + 'px ' + page_box.height_px + 'px;\n';
  683. result += ' -webkit-print-color-adjust: exact;\n';
  684. result += '}\n';
  685. result += '</style>\n';
  686. return result;
  687. }
  688. public generateHtml(cards: Card[], options: Options) {
  689. options = options || new Options();
  690. var rows = options.page_rows || 3;
  691. var cols = options.page_columns || 3;
  692. // Generate the HTML for each card
  693. var generator = new CardHtmlGenerator();
  694. var pages: CardPageSet<string> = this._generatePages(cards, options, rows, cols, generator);
  695. // Wrap all pages in a <page> element
  696. var document = this._wrap(pages, options);
  697. // Generate the HTML for the page layout
  698. var style = this._generateStyle(options);
  699. // Wrap all pages in a <page> element and add CSS for the page size
  700. var result = "";
  701. result += style
  702. result += document;
  703. return result;
  704. }
  705. public insertInto(cards: Card[], options: Options, container: HTMLElement) {
  706. // Clear the previous content of the document
  707. while (container.hasChildNodes()) {
  708. container.removeChild(container.lastChild);
  709. }
  710. // Insert the HTML
  711. var html = this.generateHtml(cards, options);
  712. container.innerHTML = html;
  713. }
  714. }
  715. }