From 6acce12bb0ae78ddc718fbd4f66d385c20d3b981 Mon Sep 17 00:00:00 2001 From: onsetsu Date: Sun, 31 Mar 2024 23:50:05 +0200 Subject: [PATCH] render all four styles in html SQUASHED: AUTO-COMMIT-demos-stefan-untitled-board-game-ubg-cards-exporter.js,AUTO-COMMIT-src-components-widgets-ubg-card.js,AUTO-COMMIT-src-components-widgets-ubg-cards.html,AUTO-COMMIT-src-components-widgets-ubg-cards.js, --- .../untitled-board-game/ubg-cards-exporter.js | 168 ++++ src/components/widgets/ubg-card.js | 780 ++++++------------ src/components/widgets/ubg-cards.html | 1 + src/components/widgets/ubg-cards.js | 27 +- 4 files changed, 462 insertions(+), 514 deletions(-) create mode 100644 demos/stefan/untitled-board-game/ubg-cards-exporter.js diff --git a/demos/stefan/untitled-board-game/ubg-cards-exporter.js b/demos/stefan/untitled-board-game/ubg-cards-exporter.js new file mode 100644 index 000000000..835d91bbc --- /dev/null +++ b/demos/stefan/untitled-board-game/ubg-cards-exporter.js @@ -0,0 +1,168 @@ +/* global globalThis */ +"disable deepeval" + +import moment from "src/external/moment.js"; + +// #TODO: remove duplication +const POKER_CARD_SIZE_INCHES = lively.pt(2.5, 3.5); +const POKER_CARD_SIZE_MM = POKER_CARD_SIZE_INCHES.scaleBy(25.4); + +export default class CardExporter { + + /*MD ## Helper MD*/ + static async printWithSavedWorld(fn) { + const oldBody = window.oldBody = Array.from(document.body.childNodes) + const bodyCSS = document.body.style.cssText + const title = document.title + + try { + await Promise.race([fn(), lively.sleep(10000)]) + + document.querySelectorAll('lively-notification-list').forEach(list => list.remove()) + const mutationIndicator = document.querySelector('#mutationIndicator') + if (mutationIndicator) { + mutationIndicator.remove() + } + document.title = `cards-${moment().format('YYYY-MM-DD-HH-mm-ss')}` + + return window.print() + } finally { + document.body.innerHTML = "" + document.body.style = bodyCSS + document.body.append(...oldBody) + document.title = title + } + } + + /*MD ## Layout & Rendering MD*/ + static async execute(cards, ubgCards) { + const cardsToPrint = cards.slice(0, 14) + + this.printWithSavedWorld(async () => { + const body = document.body + body.innerHTML = '' + // body.style = "" + + await this.buildCards(undefined, cardsToPrint, false, ubgCards) + }) + } + + static addGridPage() { + const grid =
; + document.body.append(
{grid}
); + return grid; + } + + static placeHolder() { + return
placeholder
+ } + + static cardBack(card) { + return
{card.getName()}
+ } + + static async renderCard(card, ubgCards) { + const ubg = ubgCards; + + const cardPreview = document.createElement('ubg-card') + cardPreview.setCard(card) + cardPreview.src = ubg.src + await cardPreview.render() + return cardPreview + } + + static async buildCards(doc, cardsToPrint, skipCardBack, ubgCards) { + const GAP = lively.pt(.2, .2); + const A4_WIDTH = 210 + const A4_HEIGHT = 297 + + const rowsPerPage = Math.max(((A4_HEIGHT + GAP.y) / (POKER_CARD_SIZE_MM.y + GAP.y)).floor(), 1); + const cardsPerRow = Math.max(((A4_WIDTH + GAP.x) / (POKER_CARD_SIZE_MM.x + GAP.x)).floor(), 1); + const cardsPerPage = rowsPerPage * cardsPerRow; + + this.prepareGridSizes(cardsPerRow) + + let currentPage = 0; + while (cardsToPrint.length > cardsPerPage * currentPage) { + const frontGrid = this.addGridPage() + const backGrid = this.addGridPage() + + for(let rowIndex = 0; rowIndex < rowsPerPage; rowIndex++) { + for(let cIndex = 0; cIndex < cardsPerRow; cIndex++) { + const card = cardsToPrint[cIndex + rowIndex * cardsPerRow + currentPage * cardsPerPage]; + if (card) { + frontGrid.append(await this.renderCard(card, ubgCards)) + } else { + frontGrid.append(this.placeHolder()) + } + + const cardBack = cardsToPrint[cardsPerRow - cIndex - 1 + rowIndex * cardsPerRow + currentPage * cardsPerPage]; + if (cardBack) { + // #TODO: cardBacks + backGrid.append(await this.cardBack(cardBack)) + } else { + backGrid.append(this.placeHolder()) + } + } + } + + currentPage++; + } + } + + static prepareGridSizes(cardsPerRow) { + const style = ; + style.textContent = ` +@page { +size: A4 portrait; +margin: 0; +} + +.page { +page-break-before: always; + + + +width: 100vw; +height: 100vh; +} + +.grid-wrapper { +display: grid; +place-items: center; +height: 100vh; +} + +.grid { + +z-index: 100; +display: grid; +gap: .2mm; +grid-template-columns: repeat(${cardsPerRow}, ${POKER_CARD_SIZE_INCHES.x}in); +width: min-content; +} +`; + document.body.append(style); + } + +} + +globalThis.__ubg_html_to_pdf_exporter__ = CardExporter diff --git a/src/components/widgets/ubg-card.js b/src/components/widgets/ubg-card.js index 712e2be82..45cc26ef1 100644 --- a/src/components/widgets/ubg-card.js +++ b/src/components/widgets/ubg-card.js @@ -110,18 +110,6 @@ if (globalThis.__ubg_font_cache__) { globalThis.__ubg_font_cache__ = new FontCache(); } -// const BASE64_FONT_AWESOME_THIN = await globalThis.__ubg_font_cache__.BASE64_FONT_AWESOME_THIN() -// const BASE64_FONT_AWESOME_LIGHT = await globalThis.__ubg_font_cache__.BASE64_FONT_AWESOME_LIGHT() -// const BASE64_FONT_AWESOME_REGULAR = await globalThis.__ubg_font_cache__.BASE64_FONT_AWESOME_REGULAR() -// const BASE64_FONT_AWESOME_SOLID = await globalThis.__ubg_font_cache__.BASE64_FONT_AWESOME_SOLID() -// const BASE64_FONT_AWESOME_BRANDS = await globalThis.__ubg_font_cache__.BASE64_FONT_AWESOME_BRANDS() -// const BASE64_FONT_AWESOME_DUOTONE = await globalThis.__ubg_font_cache__.BASE64_FONT_AWESOME_DUOTONE() - -// const BASE64_BeaufortforLOLJaBold = await globalThis.__ubg_font_cache__.BASE64_BeaufortforLOLJaBold() -// const BASE64_BeaufortforLOLJaRegular = await globalThis.__ubg_font_cache__.BASE64_BeaufortforLOLJaRegular() -// const BASE64_Univers_55 = await globalThis.__ubg_font_cache__.BASE64_Univers_55() -// const BASE64_Univers45LightItalic = await globalThis.__ubg_font_cache__.BASE64_Univers45LightItalic() - const FONT_NAME_FA_THIN_100 = 'fa-thin-100' const FONT_NAME_FA_LIGHT_300 = 'fa-light-300' const FONT_NAME_FA_REGULAR_400 = 'fa-regular-400' @@ -137,11 +125,6 @@ const FONT_NAME_UNIVERS_45_LIGHT_ITALIC = 'Univers 45 Light Italic' // Card group name (ELITE, SPIDER, YETI, etc.) -- Univers 59 // #BROKEN?? #TODO const FONT_NAME_CARD_TYPE = FONT_NAME_UNIVERS_55 -// Card name, card cost, card stats -- Beaufort for LOL Bold -const FONT_NAME_CARD_NAME = FONT_NAME_BEAUFORT_FOR_LOL_BOLD -const FONT_NAME_CARD_COST = FONT_NAME_BEAUFORT_FOR_LOL_BOLD -const FONT_NAME_CARD_VP = FONT_NAME_BEAUFORT_FOR_LOL_BOLD - // Card description -- Univers 55 const FONT_NAME_CARD_TEXT = FONT_NAME_UNIVERS_55 @@ -158,6 +141,7 @@ const CSS_FONT_FAMILY_BEAUFORT_FOR_LOL_REGULAR = "Beaufort for LOL Regular" const CSS_FONT_FAMILY_UNIVERS_55 = "Univers 55" const CSS_FONT_FAMILY_UNIVERS_45_LIGHT_ITALIC = "Univers 45 Light Italic" +// Card name, card cost, card stats -- Beaufort for LOL Bold const CSS_FONT_FAMILY_CARD_NAME = CSS_FONT_FAMILY_BEAUFORT_FOR_LOL_BOLD const CSS_FONT_FAMILY_CARD_COST = CSS_FONT_FAMILY_BEAUFORT_FOR_LOL_BOLD const CSS_FONT_FAMILY_CARD_VP = CSS_FONT_FAMILY_BEAUFORT_FOR_LOL_BOLD @@ -830,10 +814,7 @@ ${SVG.elementSymbol(others[2], lively.pt(12.5, 8.5), 1.5)}`, lively.rect(0, 0, 1 /*MD ## --- MD*/ // #important - static async renderRuleText(cardEditor, cardDesc, doc, outsideBorder, ruleBox, { - insetTextBy = 2, - beforeRenderRules = () => {} - } = { }) { + static async renderRuleText(cardEditor, cardDesc, outsideBorder, ruleBox, options) { let printedRules = cardDesc.getText() || ''; // old big cast icon with small tap @@ -904,7 +885,7 @@ ${SVG.elementSymbol(others[2], lively.pt(12.5, 8.5), 1.5)}`, lively.rect(0, 0, 1 printedRules = this.renderHedronIcon(printedRules) printedRules = this.renderTapIcon(printedRules) - return this.renderToDoc(cardEditor, outsideBorder, ruleBox, insetTextBy, printedRules, beforeRenderRules, doc) + this.renderToDoc(cardEditor, outsideBorder, ruleBox, printedRules, options) } static renderReminderText(printedRules, cardEditor, cardDesc) { @@ -1389,7 +1370,19 @@ ${textToPrint}`, undefined, undefined, 'transform:scale(1);'); }); } - static async renderToDoc(cardEditor, outsideBorder, ruleBox, insetTextBy, printedRules, beforeRenderRules, doc) { + static async renderToDoc(cardEditor, outsideBorder, ruleBox, printedRules, options) { + const { + insetBoxBy = 1, + insetTextBy = 1, + innerStrokeWidth = .2, + innerStrokeColor = 'black', + innerFillColor = 'white', + innerFillOpacity = .5, + outerStrokeColor= 'gray', + outerFillColor = 'white', + outerFillOpacity = .5, + } = options + const textShadow = `text-shadow: -1px -1px 0 #fff, 1px -1px 0 #fff, @@ -1399,103 +1392,37 @@ ${textToPrint}`, undefined, undefined, 'transform:scale(1);'); 1px 0 0 #fff, 0 1px 0 #fff, 0 -1px 0 #fff;` - - const ruleTextBox = ruleBox.insetBy(insetTextBy); - // doc.rect(ruleBox.x, ruleBox.y, ruleBox.width, ruleBox.height, 'FD') - cardEditor.debugRect(ruleTextBox) - const elementHTML =
; - elementHTML.innerHTML = printedRules; - cardEditor.content.append(elementHTML) - beforeRenderRules(undefined, elementHTML) - return; - document.body.append(elementHTML); - - const canvas = await html2canvas(elementHTML, { - backgroundColor: null, - ignoreElements: element => { - try { - if (!element) { - return true; - } - - return !(element === document.head || element.id === RUNETERRA_FONT_ID || element === document.body || element === elementHTML || elementHTML.contains(element)); - } catch (e) {} - } - }); - // elementHTML.remove(); - - const EXISTING_CANVAS_ID = 'exist-canvas'; - const EXISTING_ELEMENT_ID = 'exist-element'; - const existCanvas = document.getElementById(EXISTING_CANVAS_ID); - existCanvas && existCanvas.remove(); - document.body.appendChild(canvas); - canvas.id = EXISTING_CANVAS_ID; - - const existElement = document.getElementById(EXISTING_ELEMENT_ID); - existElement && existElement.remove(); - document.body.appendChild(elementHTML); - elementHTML.style.overflow = 'visible'; - elementHTML.id = EXISTING_ELEMENT_ID; - - const imgData = canvas.toDataURL('image/png'); - const imgRect = lively.rect(0, 0, canvas.width, canvas.height); - const scaledRect = imgRect.fitToBounds(ruleTextBox, true); - scaledRect.y = ruleTextBox.y + ruleTextBox.height - scaledRect.height; - - - doc.addImage(imgData, "PNG", ...scaledRect::xYWidthHeight()); - - return scaledRect - } -} + cardEditor.content.append(outerBox) -function justify(pdfGen, text, xStart, yStart, textWidth) { - text = text.replace(/(?:\r\n|\r|\n)/g, ' '); - text = text.replace(/ +(?= )/g, ''); - const lineHeight = pdfGen.getTextDimensions('a').h * 1.15; - const words = text.split(' '); - let lineNumber = 0; - let wordsInfo = []; - let lineLength = 0; - for (const word of words) { - const wordLength = pdfGen.getTextWidth(word + ' '); - if (wordLength + lineLength > textWidth) { - writeLine(pdfGen, wordsInfo, lineLength, lineNumber++, xStart, yStart, lineHeight, textWidth); - wordsInfo = []; - lineLength = 0; - } - wordsInfo.push({ text, wordLength }); - lineLength += wordLength; - } - if (wordsInfo.length > 0) { - writeLastLine(wordsInfo, pdfGen, xStart, yStart, lineNumber, lineHeight); - } -} -function writeLastLine(wordsInfo, pdfGen, xStart, yStart, lineNumber, lineHeight) { - const line = wordsInfo.map(x => x.text).join(' '); - pdfGen.text(line, xStart, yStart + lineNumber * lineHeight); -} - -function writeLine(pdfGen, wordsInfo, lineLength, lineNumber, xStart, yStart, lineHeight, textWidth) { + const ruleTextBox = ruleBox.insetBy(insetTextBy); + // cardEditor.debugRect(ruleTextBox) + const marginCalc = `${insetBoxBy}mm - ${innerStrokeWidth}mm / 2`; + const paddingCalc = `${insetTextBy}mm - ${innerStrokeWidth}mm / 2`; + const ruleTextElement =
; - const wordSpacing = (textWidth - lineLength) / (wordsInfo.length - 1); - let x = xStart; - const y = yStart + lineNumber * lineHeight; - for (const wordInfo of wordsInfo) { - pdfGen.text(wordInfo.text, x, y); - x += wordInfo.wordLength + wordSpacing; + ruleTextElement.innerHTML = printedRules; + outerBox.append(ruleTextElement) } } @@ -1965,86 +1892,6 @@ export class Cards extends Morph { return (await this.assetsFolder.fetchStats()).contents; } - /*MD #### Fonts MD*/ - addFont(doc, vfsName, fontName, fontDataBase64) { - // "data:font/ttf;base64," + - const fontBase64 = fontDataBase64; - doc.addFileToVFS(vfsName, fontBase64); - doc.addFont(vfsName, fontName, 'normal'); - } - - async ensureFont(doc, fontName) { - throw new Error('Attempted Font Loading ' + fontName) - if (!doc.__ubg_fonts__) { - doc.__ubg_fonts__ = {} - } - - if (doc.__ubg_fonts__[fontName]) { - lively.notify(fontName, 'existing font') - return - } - - doc.__ubg_fonts__[fontName] = true - - // convert fonts to jspdf-compatible format at https://peckconsulting.s3.amazonaws.com/fontconverter/fontconverter.html - const allFontData = { - [FONT_NAME_FA_THIN_100]: { - vfsName: 'fa-thin-100.ttf', - fontDataBase64: BASE64_FONT_AWESOME_THIN - }, - [FONT_NAME_FA_LIGHT_300]: { - vfsName: 'fa-light-300.ttf', - fontDataBase64: BASE64_FONT_AWESOME_LIGHT - }, - [FONT_NAME_FA_REGULAR_400]: { - vfsName: 'fa-regular-400.ttf', - fontDataBase64: BASE64_FONT_AWESOME_REGULAR - }, - [FONT_NAME_FA_SOLID_900]: { - vfsName: 'fa-solid-900.ttf', - fontDataBase64: BASE64_FONT_AWESOME_SOLID - }, - [FONT_NAME_FA_BRANDS_400]: { - vfsName: 'fa-brands-400.ttf', - fontDataBase64: BASE64_FONT_AWESOME_BRANDS - }, - [FONT_NAME_FA_DUOTONE_900]: { - vfsName: 'fa-duotone-900.ttf', - fontDataBase64: BASE64_FONT_AWESOME_DUOTONE - }, - - [FONT_NAME_BEAUFORT_FOR_LOL_BOLD]: { - vfsName: 'BeaufortforLOLJa-Bold-normal.ttf', - fontDataBase64: BASE64_BeaufortforLOLJaBold - }, - [FONT_NAME_BEAUFORT_FOR_LOL_REGULAR]: { - vfsName: 'BeaufortforLOLJa-Regular-normal.ttf', - fontDataBase64: BASE64_BeaufortforLOLJaRegular - }, - [FONT_NAME_UNIVERS_55]: { - vfsName: 'univers_55-normal.ttf', - fontDataBase64: BASE64_Univers_55 - }, - [FONT_NAME_UNIVERS_45_LIGHT_ITALIC]: { - vfsName: 'univers-45-light-italic.ttf', - fontDataBase64: BASE64_Univers45LightItalic - }, - } - - const fontData = allFontData[fontName] - if (!fontData) { - throw new Error('Unknown font: ' + fontName) - } - const { vfsName, fontDataBase64 } = fontData - this.addFont(doc, vfsName, fontName, fontDataBase64); - } - - async setAndEnsureFont(doc, fontName, fontStyle) { - // return; - await this.ensureFont(doc, fontName) - doc.setFont(fontName, fontStyle) - } - /*MD ### BUILD MD*/ async buildCards(doc, cardsToPrint, skipCardBack) { const GAP = lively.pt(.2, .2); @@ -2207,130 +2054,93 @@ export class Cards extends Morph { } async renderMagicStyle(doc, cardDesc, outsideBorder, assetsInfo) { - const currentVersion = cardDesc.versions.last; - const [BOX_FILL_COLOR, BOX_STROKE_COLOR, BOX_FILL_OPACITY] = this.colorsForCard(cardDesc); -// // black border -// doc::withGraphicsState(() => { -// doc.setFillColor(0.0); -// doc.roundedRect(...outsideBorder::xYWidthHeight(), 3, 3, 'F'); -// }); - -// // innerBorder -// const innerBorder = outsideBorder.insetBy(3); -// // doc.setFillColor(120, 120, 120); -// // doc.roundedRect(...innerBorder::xYWidthHeight(), 3, 3, 'FD'); - -// // id -// doc::withGraphicsState(() => { -// doc.setFontSize(7); -// doc.setTextColor(255, 255, 255); -// doc.text(`${cardDesc.id || '???'}/1`, innerBorder.right(), (innerBorder.bottom() + outsideBorder.bottom()) / 2, { align: 'right', baseline: 'middle' }); -// }); - -// // card image -// const { img, scaledRect } = await this.getBackgroundImage(doc, cardDesc, innerBorder, assetsInfo); - -// doc::withGraphicsState(() => { -// doc.rect(...innerBorder::xYWidthHeight(), null); // set clipping area -// doc.internal.write('W n'); -// doc.addImage(img, "JPEG", ...scaledRect::xYWidthHeight()); -// }); - -// // title bar -// const TITLE_BAR_HEIGHT = 7; -// const titleBar = innerBorder.insetBy(1); -// titleBar.height = TITLE_BAR_HEIGHT; -// doc::withGraphicsState(() => { -// doc.setGState(new doc.GState({ opacity: 0.5 })); -// doc.setFillColor(BOX_FILL_COLOR); -// doc.setDrawColor(BOX_STROKE_COLOR); -// doc.roundedRect(...titleBar::xYWidthHeight(), 1, 1, 'DF'); -// }); - -// // card name -// const cardName = this.getNameFromCard(cardDesc); -// doc::withGraphicsState(() => { -// doc.setFontSize(.6 * TITLE_BAR_HEIGHT::mmToPoint()); -// doc.setTextColor('#000000'); -// doc.text(cardName, ...titleBar.leftCenter().addX(2).toPair(), { align: 'left', baseline: 'middle' }); -// }); -// // doc.text(['hello world', 'this is a card'], ...titleBar.leftCenter().addX(2).toPair(), { align: 'left', baseline: 'middle' }); - -// // cost -// const cost = cardDesc.getCost(); -// const costs = Array.isArray(cost) ? cost : [cost]; -// let top = titleBar.bottom() + 1; -// let right = titleBar.right(); -// const COIN_RADIUS = 4; -// costs.forEach((cost, i) => { -// const coinCenter = lively.pt(right - COIN_RADIUS, top + COIN_RADIUS + COIN_RADIUS * 2 * 0.9 * i); - -// doc::withGraphicsState(() => { -// doc.setGState(new doc.GState({ opacity: 0.9 })); -// doc.setFillColor('#b8942d'); -// doc.setDrawColor('#b8942d'); -// doc.ellipse(...coinCenter.toPair(), COIN_RADIUS, COIN_RADIUS, 'DF'); -// }); - -// if (cost !== undefined) { -// doc::withGraphicsState(() => { -// doc.setFontSize(12); -// doc.setTextColor('#000000'); -// doc.text('' + cost, ...coinCenter.toPair(), { align: 'center', baseline: 'middle' }); -// }); -// } -// }); - -// // rule box -// const ruleBox = innerBorder.insetBy(1); -// const height = innerBorder.height * .4; -// ruleBox.y = ruleBox.bottom() - height; -// ruleBox.height = height; -// doc::withGraphicsState(() => { -// doc.setGState(new doc.GState({ opacity: BOX_FILL_OPACITY })); -// doc.setFillColor(BOX_FILL_COLOR); -// doc.setDrawColor(BOX_STROKE_COLOR); -// doc.roundedRect(...ruleBox::xYWidthHeight(), 1, 1, 'DF'); -// }); - -// // rule text -// const ubgTest = document.querySelector('#ubg-test'); -// if (ubgTest && false) { -// lively.notify(244); -// await new Promise((resolve, reject) => { -// doc.html(ubgTest, { -// callback: resolve, -// x: ruleBox.x, -// y: ruleBox.y -// }); -// }); -// } else {} -// // const ruleTextBox = ruleBox.insetBy(2); -// // doc.setFontSize(9); -// // doc.setTextColor('#000000'); -// // doc.text(currentVersion.text, ...ruleTextBox.topLeft().toPair(), { align: 'left', baseline: 'top', maxWidth: ruleTextBox.width }); - - -// // type & elements -// doc::withGraphicsState(() => { -// doc.setFontSize(7); -// doc.setTextColor(255, 255, 255); -// doc.text(`${currentVersion.type || ''} - ${currentVersion.elements || currentVersion.element || ''}`, ruleBox.left(), ruleBox.top() - .5, { align: 'justify', baseline: 'bottom' }); -// }); - -// await this.renderRuleText(doc, cardDesc, outsideBorder, ruleBox, { -// insetTextBy: 2 -// }); - -// // tags -// const tagsAnchor = ruleBox.topRight().subY(1); -// await this.renderTags(doc, cardDesc, tagsAnchor, outsideBorder) + // black border + this.roundedRect(outsideBorder, 'black', 'transparent', '0', '0') + + // innerBorder + const INNER_INSET = 3; + const innerBorder = outsideBorder.insetBy(INNER_INSET); + + // id + this.renderId(cardDesc) + this.get('#id-version').style.color = 'white' + + // card image + const filePath = this.filePathForBackgroundImage(cardDesc, assetsInfo); + const newBG =
; + this.content.append(newBG) + + // title bar + const TITLE_BAR_HEIGHT = 7; + const titleBar = innerBorder.insetBy(1); + titleBar.height = TITLE_BAR_HEIGHT; + const TITLE_BAR_BORDER_WIDTH = 0.200025; + this.roundedRect(titleBar, this.colorWithOpacity(BOX_FILL_COLOR, .5), BOX_STROKE_COLOR, TITLE_BAR_BORDER_WIDTH, 1) + + // card name + { + const pos = titleBar.leftCenter().addX(2); + const fontSize = .6 * titleBar.height::mmToPoint(); + + const cardName = this.getNameFromCard(cardDesc); + this.content.append({cardName}) + } + + // cost + const COIN_RADIUS = 4; + const coinPos = titleBar.bottomLeft().addY(1).addXY(COIN_RADIUS, COIN_RADIUS); + this.renderCost(doc, cardDesc, coinPos, COIN_RADIUS) + + // type & elements + const typePos = coinPos.addY(COIN_RADIUS * 1.5) + await this.renderType(cardDesc, typePos, BOX_FILL_COLOR, BOX_FILL_OPACITY) + + // rule box + const ruleBox = outsideBorder.copy() + const height = outsideBorder.height * .4; + ruleBox.y = ruleBox.bottom() - height; + ruleBox.height = height; + this.debugRect(ruleBox) + const ruleBoxInset = 1 + INNER_INSET; + const ruleTextInset = 2; + await this.renderRuleText(cardDesc, outsideBorder, ruleBox, { + insetBoxBy: ruleBoxInset, + insetTextBy: ruleTextInset, + innerStrokeColor: BOX_STROKE_COLOR, + innerFillColor: BOX_FILL_COLOR, + innerFillOpacity: BOX_FILL_OPACITY, + outerStrokeColor: 'transparent', + outerFillColor: 'transparent', + outerFillOpacity: 0, + }); + + // tags + const tagsAnchor = titleBar.bottomRight().addY(1); + await this.renderTags(cardDesc, tagsAnchor, outsideBorder) } async renderFullBleedStyle(doc, cardDesc, outsideBorder, assetsInfo) { - // #CONTINUE const type = cardDesc.getType(); const typeString = type && type.toLowerCase && type.toLowerCase() || ''; @@ -2344,8 +2154,8 @@ export class Cards extends Morph { await this.renderMagicStyle(doc, cardDesc, outsideBorder, assetsInfo) } - this.renderIsBad(doc, cardDesc, outsideBorder) - this.renderVersionIndicator(doc, cardDesc, outsideBorder) + this.renderIsBad(cardDesc, outsideBorder) + this.renderVersionIndicator(cardDesc, outsideBorder) } /*MD ### Rendering Card Types MD*/ @@ -2354,90 +2164,67 @@ export class Cards extends Morph { const [BOX_FILL_COLOR, BOX_STROKE_COLOR, BOX_FILL_OPACITY] = this.colorsForCard(cardDesc); // background card image -// const { img, scaledRect } = await this.getBackgroundImage(doc, cardDesc, outsideBorder, assetsInfo); -// this.withinCardBorder(doc, outsideBorder, () => { -// doc.addImage(img, "JPEG", ...scaledRect::xYWidthHeight()); -// }); - -// this.withinCardBorder(doc, outsideBorder, () => { -// doc::withGraphicsState(() => { -// doc.setGState(new doc.GState({ opacity: BOX_FILL_OPACITY })); -// doc.setFillColor(BOX_FILL_COLOR); -// doc.rect(...outsideBorder::xYWidthHeight(), 'F'); -// }); -// }) - -// // spell circle -// { -// const CIRCLE_BORDER = -3; -// const RADIUS = (outsideBorder.width - CIRCLE_BORDER) / 2; -// const middle = outsideBorder.center().withY(outsideBorder.top() + CIRCLE_BORDER + RADIUS) - -// // console.log(doc.getLineWidth()) -// this.withinCardBorder(doc, outsideBorder, () => { -// doc::withGraphicsState(() => { -// doc.circle(...middle.toPair(), RADIUS, null); -// doc.internal.write('W n'); - -// doc.addImage(img, "JPEG", ...scaledRect::xYWidthHeight()); - -// doc.setDrawColor(BOX_STROKE_COLOR); -// doc.setLineWidth(2) -// doc.circle(...middle.toPair(), RADIUS, 'D'); -// }) -// }) -// } + this.setBackgroundImage(cardDesc, assetsInfo) -// // innerBorder + // spell circle + { + const CIRCLE_BORDER = -3; + const RADIUS = (outsideBorder.width - CIRCLE_BORDER) / 2; + const middle = outsideBorder.center().withY(outsideBorder.top() + CIRCLE_BORDER + RADIUS) + const strokeWidth = 2; + const svg = + + + + + + + + + ; + + this.content.insertAdjacentHTML('beforeend', svg.outerHTML) + } + + // innerBorder const innerBorder = outsideBorder.insetBy(3); - // doc.setFillColor(120, 120, 120); - // doc.roundedRect(...innerBorder::xYWidthHeight(), 3, 3, 'FD'); -// // title -// const TITLE_BAR_HEIGHT = 7; -// const COST_COIN_RADIUS = 4; -// const COST_COIN_MARGIN = 2; + // title + const TITLE_BAR_HEIGHT = 7; + const COST_COIN_RADIUS = 4; + const COST_COIN_MARGIN = 2; -// const titleBorder = innerBorder.insetBy(1); -// titleBorder.height = TITLE_BAR_HEIGHT; - -// await this.renderTitleBarAndCost(doc, cardDesc, titleBorder, COST_COIN_RADIUS, COST_COIN_MARGIN) - -// // rule box -// const ruleBox = outsideBorder.copy() -// const height = outsideBorder.height * .3; -// ruleBox.y = ruleBox.bottom() - height; -// ruleBox.height = height; -// // const ruleBox = innerBorder.insetBy(1); -// // const height = innerBorder.height * .4; -// // ruleBox.y = ruleBox.bottom() - height; -// // ruleBox.height = height; -// this.withinCardBorder(doc, outsideBorder, () => { -// doc::withGraphicsState(() => { -// doc.setGState(new doc.GState({ opacity: BOX_FILL_OPACITY })); -// doc.setFillColor(BOX_FILL_COLOR); -// // doc.rect(...ruleBox::xYWidthHeight(), 'F'); -// }) -// }) - -// doc::withGraphicsState(() => { -// doc.setLineWidth(1); -// doc.setDrawColor(BOX_STROKE_COLOR); -// doc.setLineDashPattern([2,1], 0); -// // doc.line(ruleBox.left(), ruleBox.top(), ruleBox.right(), ruleBox.top()); -// }); - -// // rule text -// const ruleTextBox = await this.renderRuleText(doc, cardDesc, outsideBorder, ruleBox, { -// insetTextBy: 2 -// }); - -// // tags -// const tagsAnchor = ruleTextBox.topRight(); -// await this.renderTags(doc, cardDesc, tagsAnchor, outsideBorder) + const titleBorder = innerBorder.insetBy(1); + titleBorder.height = TITLE_BAR_HEIGHT; + + await this.renderTitleBarAndCost(doc, cardDesc, titleBorder, COST_COIN_RADIUS, COST_COIN_MARGIN) + + // rule box + const ruleBox = outsideBorder.copy() + const height = outsideBorder.height * .4; + ruleBox.y = ruleBox.bottom() - height; + ruleBox.height = height; + this.debugRect(ruleBox) + // rule text + const RULE_BOX_INSET = 1; + const RULE_TEXT_INSET = 1; + await this.renderRuleText(cardDesc, outsideBorder, ruleBox, { + insetBoxBy: RULE_BOX_INSET, + insetTextBy: RULE_TEXT_INSET, + outerStrokeColor: 'transparent', + outerFillColor: 'transparent', + outerFillOpacity: 0, + }); + + // tags + const tagsAnchor = lively.pt(titleBorder.right(), titleBorder.bottom()).addXY(-RULE_TEXT_INSET, 1); + await this.renderTags(cardDesc, tagsAnchor, outsideBorder) + // id - this.renderId(doc, cardDesc, outsideBorder, innerBorder) + this.renderId(cardDesc) } line(start, end, color, width) { @@ -2484,15 +2271,17 @@ export class Cards extends Morph { return `color-mix(in srgb, ${color} ${opacity * 100}%, transparent)` } + setBackgroundImage(cardDesc, assetsInfo) { + const filePath = this.filePathForBackgroundImage(cardDesc, assetsInfo); + this.get('#bg').style.backgroundImage = `url(${filePath})` + } + // #important async renderGadget(doc, cardDesc, outsideBorder, assetsInfo) { const [BOX_FILL_COLOR, BOX_STROKE_COLOR, BOX_FILL_OPACITY] = this.colorsForCard(cardDesc); // background card image - { - const filePath = this.filePathForBackgroundImage(cardDesc, assetsInfo); - this.get('#bg').style.backgroundImage = `url(${filePath})` - } + this.setBackgroundImage(cardDesc, assetsInfo) // innerBorder const innerBorder = outsideBorder.insetBy(3); @@ -2507,15 +2296,17 @@ export class Cards extends Morph { this.line(topBox.bottomLeft(), topBox.bottomRight(), BOX_STROKE_COLOR, 1) } -// // title - const TITLE_BAR_HEIGHT = 7; - const COST_COIN_RADIUS = 4; - const COST_COIN_MARGIN = 2; - - const titleBorder = innerBorder.insetBy(1); - titleBorder.height = TITLE_BAR_HEIGHT; - - await this.renderTitleBarAndCost(doc, cardDesc, titleBorder, COST_COIN_RADIUS, COST_COIN_MARGIN) + // title + { + const TITLE_BAR_HEIGHT = 7; + const COST_COIN_RADIUS = 4; + const COST_COIN_MARGIN = 2; + + const titleBorder = innerBorder.insetBy(1); + titleBorder.height = TITLE_BAR_HEIGHT; + + await this.renderTitleBarAndCost(doc, cardDesc, titleBorder, COST_COIN_RADIUS, COST_COIN_MARGIN) + } // rule box border calc const ruleBox = outsideBorder.copy() @@ -2525,44 +2316,22 @@ export class Cards extends Morph { this.debugRect(ruleBox) // rule text - const RULE_TEXT_INSET = 2; - let effectiveRuleBox - const ruleTextBox = await this.renderRuleText(doc, cardDesc, outsideBorder, ruleBox, { + const RULE_BOX_INSET = 1; + const RULE_TEXT_INSET = 1; + await this.renderRuleText(cardDesc, outsideBorder, ruleBox, { + insetBoxBy: RULE_BOX_INSET, insetTextBy: RULE_TEXT_INSET, - beforeRenderRules: (ruleTextBox, rulesElement) => { - // rule box render - const rulesHeight = window.getComputedStyle(rulesElement).getPropertyValue("height") - const div =
- rulesElement.insertAdjacentElement('beforebegin', div); - - const line =
- rulesElement.insertAdjacentElement('beforebegin', line); - } + outerStrokeColor: BOX_STROKE_COLOR, + outerFillColor: BOX_FILL_COLOR, + outerFillOpacity: BOX_FILL_OPACITY, }); // tags const tagsAnchor = lively.pt(topBox.right(), topBox.bottom()).addXY(-RULE_TEXT_INSET, 1); - await this.renderTags(doc, cardDesc, tagsAnchor, outsideBorder) + await this.renderTags(cardDesc, tagsAnchor, outsideBorder) // id - this.renderId(doc, cardDesc, outsideBorder, innerBorder) + this.renderId(cardDesc) } // #important @@ -2570,77 +2339,69 @@ background: ${BOX_STROKE_COLOR}; const [BOX_FILL_COLOR, BOX_STROKE_COLOR, BOX_FILL_OPACITY] = this.colorsForCard(cardDesc); // background card image -// const { img, scaledRect } = await this.getBackgroundImage(doc, cardDesc, outsideBorder, assetsInfo); -// this.withinCardBorder(doc, outsideBorder, () => { -// doc.addImage(img, "JPEG", ...scaledRect::xYWidthHeight()); -// }); - -// // Zohar design -// const ZOHAR_DESIGN_BORDER_WIDTH = .5; -// [[outsideBorder.topLeft(), lively.pt(1, 0)], [outsideBorder.topRight(), lively.pt(-1, 0)]].forEach(([startingPt, direction]) => { -// const dirX = direction.x; -// this.withinCardBorder(doc, outsideBorder, () => { -// doc::withGraphicsState(() => { -// doc.setGState(new doc.GState({ opacity: 0.5 })); -// doc.setFillColor(BOX_FILL_COLOR); -// doc.setDrawColor(BOX_STROKE_COLOR); -// doc.setLineWidth(ZOHAR_DESIGN_BORDER_WIDTH); -// doc.lines([[dirX*8,0],[0,15],[-dirX*15,15],[dirX*15,15],[0,100], [-dirX*10,0]], ...startingPt.toPair(), [1,1], 'DF', true) -// }); -// }); -// }) + this.setBackgroundImage(cardDesc, assetsInfo) + + // Zohar design + { + const ZOHAR_DESIGN_BORDER_WIDTH = .5; + [[outsideBorder.topLeft(), lively.pt(1, 0)], [outsideBorder.topRight(), lively.pt(-1, 0)]].forEach(([startingPt, direction]) => { + const dirX = direction.x; + startingPt = startingPt.subY(5) + const topMost = startingPt.addXY(dirX*8, 0); + const triangleTop = topMost.addXY(0, 15 + 5); + const triangleOuter = triangleTop.addXY(-dirX*15, 15); + const triangleBottom = triangleOuter.addXY(dirX*15, 15); + const bottom = triangleBottom.addXY(0, 100); + const bottomOuter = bottom.addXY(-dirX*10, 0); + const diamondPoints = `${startingPt.x} ${startingPt.y}, ${topMost.x} ${topMost.y}, ${triangleTop.x} ${triangleTop.y}, ${triangleOuter.x} ${triangleOuter.y}, ${triangleBottom.x} ${triangleBottom.y}, ${bottom.x} ${bottom.y}, ${bottomOuter.x} ${bottomOuter.y}`; + + const svg = + + ; + + this.content.insertAdjacentHTML('beforeend', svg.outerHTML) + }) + } // innerBorder const innerBorder = outsideBorder.insetBy(3); - // doc.setFillColor(120, 120, 120); - // doc.roundedRect(...innerBorder::xYWidthHeight(), 3, 3, 'FD'); + // this.roundedRect(innerBorder, 'steelblue', 'red', 3, 0) -// // title -// const TITLE_BAR_HEIGHT = 7; -// const COST_COIN_RADIUS = 4; -// const COST_COIN_MARGIN = 2; + // title + const TITLE_BAR_HEIGHT = 7; + const COST_COIN_RADIUS = 4; + const COST_COIN_MARGIN = 2; -// const titleBorder = innerBorder.insetBy(1); -// titleBorder.height = TITLE_BAR_HEIGHT; - -// await this.renderTitleBarAndCost(doc, cardDesc, titleBorder, COST_COIN_RADIUS, COST_COIN_MARGIN) - -// // rule box border calc -// const ruleBox = outsideBorder.copy() -// const height = outsideBorder.height * .4; -// ruleBox.y = ruleBox.bottom() - height; -// ruleBox.height = height; + const titleBorder = innerBorder.insetBy(1); + titleBorder.height = TITLE_BAR_HEIGHT; -// // rule text -// const RULE_TEXT_INSET = 2; -// let effectiveRuleBox -// const ruleTextBox = await this.renderRuleText(doc, cardDesc, outsideBorder, ruleBox, { -// insetTextBy: RULE_TEXT_INSET, -// beforeRenderRules: ruleTextBox => { -// // rule box render -// effectiveRuleBox = ruleTextBox.insetBy(-RULE_TEXT_INSET) -// this.withinCardBorder(doc, outsideBorder, () => { -// doc::withGraphicsState(() => { -// doc.setGState(new doc.GState({ opacity: BOX_FILL_OPACITY })); -// doc.setFillColor(BOX_FILL_COLOR); -// doc.rect(...effectiveRuleBox::xYWidthHeight(), 'F'); -// }) -// }) - -// doc::withGraphicsState(() => { -// doc.setLineWidth(1); -// doc.setDrawColor(BOX_STROKE_COLOR); -// doc.line(effectiveRuleBox.left(), effectiveRuleBox.top(), effectiveRuleBox.right(), effectiveRuleBox.top()); -// }); -// } -// }); + await this.renderTitleBarAndCost(doc, cardDesc, titleBorder, COST_COIN_RADIUS, COST_COIN_MARGIN) -// // tags -// const tagsAnchor = lively.pt(ruleTextBox.right(), effectiveRuleBox.top()).subY(1); -// await this.renderTags(doc, cardDesc, tagsAnchor, outsideBorder) + // rule box border calc + const ruleBox = outsideBorder.copy() + const height = outsideBorder.height * .4; + ruleBox.y = ruleBox.bottom() - height; + ruleBox.height = height; + + // rule text + const RULE_BOX_INSET = 1; + const RULE_TEXT_INSET = 1; + await this.renderRuleText(cardDesc, outsideBorder, ruleBox, { + insetBoxBy: RULE_BOX_INSET, + insetTextBy: RULE_TEXT_INSET, + outerStrokeColor: BOX_STROKE_COLOR, + outerFillColor: BOX_FILL_COLOR, + outerFillOpacity: BOX_FILL_OPACITY, + }); + + // tags + const tagsAnchor = lively.pt(titleBorder.right(), titleBorder.bottom()).addXY(-RULE_TEXT_INSET, 1); + await this.renderTags(cardDesc, tagsAnchor, outsideBorder) // id - this.renderId(doc, cardDesc, outsideBorder, innerBorder) + this.renderId(cardDesc) } /*MD ### Rendering Card Components MD*/ @@ -2722,7 +2483,7 @@ font-family: "${CSS_FONT_FAMILY_CARD_NAME}"; // type currentCenter = currentCenter.addY(costCoinRadius * .75) const [BOX_FILL_COLOR, BOX_STROKE_COLOR, BOX_FILL_OPACITY] = this.colorsForCard(cardDesc); - await this.renderType(doc, cardDesc, currentCenter, BOX_FILL_COLOR, BOX_FILL_OPACITY) + await this.renderType(cardDesc, currentCenter, BOX_FILL_COLOR, BOX_FILL_OPACITY) } async renderElementList(doc, cardDesc, pos, radius, direction) { @@ -2811,8 +2572,8 @@ font-family: "${font}"; } // #important - async renderRuleText(doc, cardDesc, outsideBorder, ruleBox, options = { }) { - return RuleTextRenderer.renderRuleText(this, cardDesc, doc, outsideBorder, ruleBox, options) + async renderRuleText(cardDesc, outsideBorder, ruleBox, options) { + return RuleTextRenderer.renderRuleText(this, cardDesc, outsideBorder, ruleBox, options) } debugPoint(pt, color = 'red') { @@ -2833,7 +2594,7 @@ background: ${color}; } // type - async renderType(doc, cardDesc, anchorPt, color, opacity) { + async renderType(cardDesc, anchorPt, color, opacity) { // function curate() { // return this.toLower().upperFirst(); // } @@ -2867,7 +2628,7 @@ background: ${color}; `}>{fullText}); } - renderTags(doc, cardDesc, tagsAnchor, outsideBorder) { + renderTags(cardDesc, tagsAnchor, outsideBorder) { const tags = cardDesc.getTags().sortBy(i => i, true).map(tag =>
#{tag}
); const FONT_SIZE = 7; @@ -2893,10 +2654,7 @@ font-family: ${CSS_FONT_FAMILY_UNIVERS_55}; this.content.insertAdjacentHTML('beforeend', yourSvgString) } - renderId(doc, cardDesc, outsideBorder, innerBorder, color = '000') { - // doc.setFontSize(7); - // doc.setTextColor(color); - // doc.text(`${cardDesc.id || '???'}/${cardDesc.getHighestVersion()}`, innerBorder.right() - 2, (innerBorder.bottom() + outsideBorder.bottom()) / 2, { align: 'right', baseline: 'middle' }); + renderId(cardDesc) { this.get('#card-id').innerHTML = cardDesc.id || '???' this.get('#card-version').innerHTML = cardDesc.getHighestVersion() } @@ -2905,7 +2663,7 @@ font-family: ${CSS_FONT_FAMILY_UNIVERS_55}; return this.get('#content'); } - renderIsBad(doc, cardDesc, outsideBorder) { + renderIsBad(cardDesc, outsideBorder) { const slash = (color, width=2, offset=lively.pt(0,0)) => { const start = outsideBorder.topRight().addPt(offset); const end = outsideBorder.bottomLeft().addPt(offset); @@ -2929,7 +2687,7 @@ font-family: ${CSS_FONT_FAMILY_UNIVERS_55}; } } - renderVersionIndicator(doc, cardDesc, outsideBorder) { + renderVersionIndicator(cardDesc, outsideBorder) { const VERSION_FILL = '#f7d359'; this.get('#version-indicator').style.setProperty("--version-fill", VERSION_FILL); } diff --git a/src/components/widgets/ubg-cards.html b/src/components/widgets/ubg-cards.html index 327e6cac0..ecd71c9eb 100644 --- a/src/components/widgets/ubg-cards.html +++ b/src/components/widgets/ubg-cards.html @@ -147,6 +147,7 @@ + diff --git a/src/components/widgets/ubg-cards.js b/src/components/widgets/ubg-cards.js index 74b055d43..3e5e4191c 100644 --- a/src/components/widgets/ubg-cards.js +++ b/src/components/widgets/ubg-cards.js @@ -11,6 +11,7 @@ import 'https://lively-kernel.org/lively4/ubg-assets/load-assets.js'; import { serialize, deserialize } from 'src/client/serialize.js'; import Card from 'demos/stefan/untitled-board-game/ubg-card.js'; +import 'demos/stefan/untitled-board-game/ubg-cards-exporter.js'; import preloaWebComponents from 'src/client/preload-components.js' await preloaWebComponents(['ubg-card']) @@ -2024,6 +2025,7 @@ export default class Cards extends Morph { /*MD ### BUILD MD*/ async buildCards(doc, cardsToPrint, skipCardBack) { + globalThis.doc = doc const GAP = lively.pt(.2, .2); const rowsPerPage = Math.max(((doc.internal.pageSize.getHeight() + GAP.y) / (POKER_CARD_SIZE_MM.y + GAP.y)).floor(), 1); @@ -2279,6 +2281,7 @@ export default class Cards extends Morph { ruleBox.y = ruleBox.bottom() - height; ruleBox.height = height; doc::withGraphicsState(() => { + doc.get doc.setGState(new doc.GState({ opacity: BOX_FILL_OPACITY })); doc.setFillColor(BOX_FILL_COLOR); doc.setDrawColor(BOX_STROKE_COLOR); @@ -3275,10 +3278,23 @@ export default class Cards extends Morph { const cardsToPrint = this.filterCardsForPrinting(filteredEntries.map(entry => entry.card)) if (await this.checkForLargePrinting(cardsToPrint)) { - await this.printForExport(cardsToPrint, evt.shiftKey); + await this.printForExport(cardsToPrint, evt.shiftKey, false); } } + async onPrintSelectedNew() { + if (!this.cards) { + return; + } + + const filteredEntries = this.allEntries.filter(entry => entry.isVisible()) + const cardsToPrint = this.filterCardsForPrinting(filteredEntries.map(entry => entry.card)) + + if (await this.checkForLargePrinting(cardsToPrint)) { + await this.printForExport(cardsToPrint, false, true); + } + } + async onPrintChanges(evt) { if (!this.cards) { return; @@ -3287,7 +3303,7 @@ export default class Cards extends Morph { const cardsToPrint = this.filterCardsForPrinting(this.cards.filter(card => !card.getIsPrinted())); if (await this.checkForLargePrinting(cardsToPrint)) { - await this.printForExport(cardsToPrint, evt.shiftKey); + await this.printForExport(cardsToPrint, evt.shiftKey, false); } } @@ -3299,7 +3315,7 @@ export default class Cards extends Morph { return true; } - async printForExport(cards, quickSavePDF) { + async printForExport(cards, quickSavePDF, useHTMLPrinting) { if (cards.length === 0) { lively.warn('no cards to print for export'); return; @@ -3318,6 +3334,11 @@ export default class Cards extends Morph { this.markAsChanged() } + if (useHTMLPrinting) { + await globalThis.__ubg_html_to_pdf_exporter__.execute(cards, this) + return; + } + const doc = await this.buildFullPDF(cards); if (quickSavePDF) { this.quicksavePDF(doc);