// https://github.com/shardofhonor/dominion-card-generator/blob/master/docs/main.js let templateSize = 0; //save globally let useCORS = true; // flag to activate loading of external images via CORS helper function -> otherwise canvas is tainted and download button not working //const CORS_ANYWHERE_BASE_URL = 'https://dominion-card-generator-cors.herokuapp.com/'; //const CORS_ANYWHERE_BASE_URL = 'https://thingproxy.freeboard.io/fetch/'; const CORS_ANYWHERE_BASE_URL = 'https://proxy.cors.sh/'; // from https://blog.grida.co/cors-anywhere-for-everyone-free-reliable-cors-proxy-service-73507192714e // Initialization of complete logic on load of page function initCardImageGenerator() { //these three can all be expanded as you see fit var icons = { //the names should match the image filenames (plus a .png extension). "@": ["Debt", "white", "Treasure"], "\\^": ["Potion", "white", "Treasure"], "%": ["VP", "white", "Victory"], "#": ["VP-Token", "white", "Victory"], //German VP Token (not a nice decision of ASS Altenburger, but maybe nice to have to keep the cards consistent) "\\$": ["Coin", "black", "Treasure"], "§": ["Custom Icon", "white", "Treasure"] }; var normalColorFactorLists = [ ["Action/Event", [1, 1, 1]], ["Treasure", [1.1, 0.95, 0.55]], ["Victory", [0.75, 0.9, 0.65]], ["Reaction", [0.65, 0.8, 1.05]], ["Duration", [1.2, 0.8, 0.4]], ["Reserve", [0.9, 0.75, 0.5]], ["Curse", [0.85, 0.6, 1.1]], ["Shelter", [1.05, 0.65, 0.5]], ["Ruins", [0.75, 0.6, 0.35]], ["Landmark", [0.45, 1.25, 0.85]], ["Night", [0.3, 0.4, 0.45]], ["Boon", [1.4, 1.35, 0.55, 0, 0, 0, 1.7, 1.25, 0.65, 1.95, 1.6, 0.4]], ["Hex", [0.75, 0.6, 2.1, 0, 0, 0, 0.8, 0.8, 0.8, 1.0, 0.75, 2.1]], ["State", [1.1, 1.3, 1.3, 0.6, 0.15, 0, 1.55, 1.15, 1.05, 1.4, 0.65, 0.45]], ["Artifact", [1.15, 1, 0.75, 0.3, 0.15, 0.05]], ["Project", [1.15, 0.95, 0.9, 0.4, 0.2, 0.15]], ["Way", [1, 1.15, 1.25, 0.25, 0.3, 0.35, 1.6, 1.6, 1.6, 1.3, 1.3, 1.3]], ["Ally", [1, 0.95, 0.85, 0.35, 0.3, 0.15, 0.9, 0.8, 0.7, 0.9, 0.8, 0.7]], ["Trait", [0.95, 0.8, 1.1, 0.3, 0.25, 0.35, 1.6, 1.6, 1.6, 1.3, 1.3, 1.3]] ]; var boldableKeywords = [ //case-insensitive "card", "buy", "action", "coffer", "villager", "aktion", "aktionen", "karte", "karten", "kauf", "käufe", "dorfbewohner", "münze", "münzen" ]; var specialBoldableKeywords = [ "favor", "gefallen" ]; var travellerTypesPattern = new RegExp(["Traveller", "Traveler", "Reisender", "Reisende", "Reiziger", "Matkaaja", "Itinérant", "Путешественник", "Приключенец"].join("|")); var normalColorCustomIndices = [0, 0]; var normalColorDropdowns = document.getElementsByName("normalcolor"); for (var j = 0; j < normalColorDropdowns.length; ++j) { for (var i = 0; i < normalColorFactorLists.length; ++i) { //"- j" because only the first dropdown should have Night var option = document.createElement("option"); option.textContent = normalColorFactorLists[i][0]; normalColorDropdowns[j].appendChild(option); } normalColorCustomIndices[j] = normalColorDropdowns[j].childElementCount; var customOption = document.createElement("option"); customOption.textContent = "CUSTOM"; normalColorDropdowns[j].appendChild(customOption); customOption = document.createElement("option"); customOption.textContent = "EXTRA CUSTOM"; normalColorDropdowns[j].appendChild(customOption); normalColorDropdowns[j].selectedIndex = 0; } //var templateSize = 0; function rebuildBoldLinePatternWords() { let elemBoldkeys = document.getElementById("boldkeys"); let customBoldableKeywords = elemBoldkeys !== null ? elemBoldkeys.value : ""; let boldableKeywordsFull = customBoldableKeywords.length > 0 ? boldableKeywords.concat(customBoldableKeywords.split(";")) : boldableKeywords; boldableKeywordsFull.forEach(function (word, index) { this[index] = word.trim(); }, boldableKeywordsFull); boldLinePatternWords = RegExp("(?:([-+]\\d+)\\s+|(\\+))(" + boldableKeywordsFull.join("|") + "s?)", "ig"); boldLinePatternWordsSpecial = RegExp("(?:([-+]\\d+)\\s+|(?:(\\d+)\\s+)|(\\+)|)(" + specialBoldableKeywords.join("|") + "s?)", "ig"); } var boldLinePatternWords; var boldLinePatternWordsSpecial; rebuildBoldLinePatternWords(); var iconList = "[" + Object.keys(icons).join("") + "]"; //var boldLinePatternIcons = RegExp("[-+]\\d+\\s" + iconList + "\\d+", "ig"); var iconWithNumbersPattern = "[-+]?(" + iconList + ")([\\d\\?]*[-+\\*]?)"; var iconWithNumbersPatternSingle = RegExp("^([-+]?\\d+)?" + iconWithNumbersPattern + "(\\S*)$"); iconWithNumbersPattern = RegExp(iconWithNumbersPattern, "g"); var canvases = document.getElementsByClassName("myCanvas"); var images = []; var imagesLoaded = false; var recolorFactorList = [ [0.75, 1.1, 1.35, 0, 0, 0, 1, 2, 3, 4, 5, 6], [0.75, 1.1, 1.35, 0, 0, 0, 1, 2, 3, 4, 5, 6] ]; var normalColorCurrentIndices = [0, 0]; var recoloredImages = []; function draw() { function getRecoloredImage(imageID, colorID, offset) { if (!recoloredImages[imageID]) { //http://stackoverflow.com/questions/1445862/possible-to-use-html-images-like-canvas-with-getimagedata-putimagedata var cnvs = document.createElement("canvas"); var w = images[imageID].width, h = images[imageID].height; cnvs.width = w; cnvs.height = h; var ctx = cnvs.getContext("2d"); ctx.drawImage(images[imageID], 0, 0); var imgdata = ctx.getImageData(0, 0, w, h); var rgba = imgdata.data; offset = offset || 0; var recolorFactors; if (normalColorCurrentIndices[colorID] === normalColorCustomIndices[colorID]) recolorFactors = recolorFactorList[colorID].slice(0, 3); else if (normalColorCurrentIndices[colorID] > normalColorCustomIndices[colorID]) recolorFactors = recolorFactorList[colorID]; else recolorFactors = normalColorFactorLists[normalColorCurrentIndices[colorID] - colorID][1]; recolorFactors = recolorFactors.slice(); while (recolorFactors.length < 6) recolorFactors.push(0); if (offset == 0) { for (var ch = 0; ch < 3; ++ch) recolorFactors[ch] -= recolorFactors[ch + 3]; for (var px = 0, ct = w * h * 4; px < ct; px += 4) if (rgba[px + 3]) //no need to recolor pixels that are fully transparent for (var ch = 0; ch < 3; ++ch) rgba[px + ch] = Math.max(0, Math.min(255, Math.round(recolorFactors[ch + 3] * 255 + rgba[px + ch] * recolorFactors[ch]))); } else { while (recolorFactors.length < 12) recolorFactors.push(genericCustomAccentColors[templateSize & 1][recolorFactors.length]); for (var px = 0, ct = w * h * 4; px < ct; px += 4) if (rgba[px + 3]) for (var ch = 0; ch < 3; ++ch) rgba[px + ch] = Math.max(0, Math.min(255, rgba[px + ch] * recolorFactors[ch + offset])); } ctx.putImageData(imgdata, 0, 0); recoloredImages[imageID] = cnvs; } return recoloredImages[imageID]; } var iconReplacedWithSpaces = " "; function getWidthOfLineWithIconsReplacedWithSpaces(line) { return context.measureText(line.replace(iconWithNumbersPattern, iconReplacedWithSpaces)).width; } function getIconListing(icon) { return icons[icon] || icons["\\" + icon]; } var shadowDistance = 10; var italicSubstrings = ["[i]", "Heirloom: ", "Erbstück: ", "(This is not in the Supply.)", "Keep this until Clean-up."]; function writeLineWithIconsReplacedWithSpaces(line, x, y, scale, family, boldSize) { boldSize = boldSize || 64; context.textAlign = "left"; if (italicSubstrings.some(substring => line.includes(substring))) { context.font = "italic " + context.font; if (line.includes("[i]")) { line = line.split("[i]").join(""); x += boldSize * scale; } } else { context.font = context.font.replace("italic ", ""); } var words = line.split(" "); for (var i = 0; i < words.length; ++i) { var word = words[i]; context.save(); while (word) { var match = word.match(iconWithNumbersPatternSingle); if (match) { var familyOriginal = family; family = "mySpecials"; var localY = y; var localScale = scale; if (words.length === 1 && !word.startsWith('+')) { localY += 115 - scale * 48; context.font = "bold 192pt " + family; localScale = 1.6; if (templateSize === 3) { context.font = "bold 222pt " + family; if (word.includes('$')) { // Treasure Base cards localScale = localScale * 2; } else { localScale = localScale * 1.5; } } else { x = x + 48 * scale; } } var halfWidthOfSpaces = context.measureText(iconReplacedWithSpaces).width / 2 + 2; var image = false; var iconKeys = Object.keys(icons); for (var j = 0; j < iconKeys.length; ++j) { if (iconKeys[j].replace("\\", "") == match[2]) { image = images[numberFirstIcon + j]; break; } } context.save(); if (!match[1] && (match[0].charAt(0) === '+' || match[0].charAt(0) === '-')) { match[1] = match[0].charAt(0); } if (match[1]) { if (context.font[0] !== "b") context.font = "bold " + context.font; context.fillText(match[1], x, localY); x += context.measureText(match[1]).width + 10 * localScale; } x += halfWidthOfSpaces; context.translate(x, localY); context.scale(localScale, localScale); if (image && image.height) { //exists //context.shadowColor = "#000"; context.shadowBlur = 25; context.shadowOffsetX = localScale * shadowDistance; context.shadowOffsetY = localScale * shadowDistance; context.drawImage(image, image.width / -2, image.height / -2); context.shadowColor = "transparent"; } //else... well, that's pretty weird, but so it goes. if (match[3]) { //text in front of image context.textAlign = "center"; context.fillStyle = getIconListing(match[2])[1]; let cost = match[3]; let bigNumberScale = 1; let nx = localScale > 1.4 ? 0 : -5 * localScale ^ 2; let ny = localScale > 1 ? 6 * localScale : localScale > 0.7 ? 12 * localScale : localScale > 0.5 ? 24 * localScale : 48 * localScale; if (localScale > 3) { bigNumberScale = 0.8; ny -= (115 * 0.2) / 2; } if (cost.length >= 2) { // special handling for overpay and variable costs let specialCost = cost.slice(-1); let specialCostSize = 45; let syShift = 0; if (specialCost === '*') { //specialCost = '✱'; specialCostSize = 65; syShift = 10; if (cost.length > 2) { bigNumberScale = 1.5 / (cost.length - 1); } } else if (specialCost === '+') { specialCost = '✚'; specialCostSize = 40; if (cost.length > 2) { bigNumberScale = 1.5 / (cost.length - 1); } } else { specialCost = null; bigNumberScale = 1.5 / cost.length; } if (specialCost != null) { cost = cost.slice(0, -1) + " "; context.font = "bold " + specialCostSize + "pt " + family; let sx = localScale > 1 ? 45 / 2 * localScale : 45 * localScale; let sy = localScale > 1 ? -20 * localScale : 12 * localScale - 35 * localScale; if (cost.length >= 3) { nx -= specialCostSize * 1 / 3; sx += specialCostSize * 1 / 3; } sy += syShift * localScale; context.fillText(specialCost, sx, sy); } } context.font = "bold " + 115 * bigNumberScale + "pt " + family; context.fillText(cost, nx, ny); //context.strokeText(match[3], 0, 0); } context.restore(); family = familyOriginal; x += halfWidthOfSpaces; word = match[4]; } else { if (word.match(boldLinePatternWords) || word.match(boldLinePatternWordsSpecial)) { if (words.length === 1) context.font = "bold " + boldSize + "pt " + family; else context.font = "bold " + context.font; } if (context.font.includes('bold')) { let lastChar = word.substr(word.length - 1); if ([",", ";", ".", "?", "!", ":"].includes(lastChar)) { word = word.slice(0, -1); } else { lastChar = ""; } context.fillText(word, x, y); if (lastChar != "") { var x2 = context.measureText(word).width; context.font = context.font.replace('bold ', ''); context.fillText(lastChar, x + x2, y); context.font = "bold " + context.font; } word = word + lastChar; } else { context.fillText(word, x, y); } break; //don't start this again } } x += context.measureText(word + " ").width; context.restore(); } } function writeSingleLine(line, x, y, maxWidth, initialSize, family) { family = family || "myTitle"; var size = (initialSize || 85) + 2; do { context.font = (size -= 2) + "pt " + family; } while (maxWidth && getWidthOfLineWithIconsReplacedWithSpaces(line) > maxWidth); writeLineWithIconsReplacedWithSpaces(line, x - getWidthOfLineWithIconsReplacedWithSpaces(line) / 2, y, size / 90, family); } function writeDescription(elementID, xCenter, yCenter, maxWidth, maxHeight, boldSize) { rebuildBoldLinePatternWords(); var description = document.getElementById(elementID).value.replace(/ *\n */g, " \n ").replace(boldLinePatternWords, "$1\xa0$2$3").replace(boldLinePatternWordsSpecial, "$1$2\xa0$3$4") + " \n"; //separate newlines into their own words for easier processing var words = description.split(" "); var lines; var widthsPerLine; var heightsPerLine; var overallHeight; var size = 64 + 2; do { //figure out the best font size, and also decide in advance how wide and tall each individual line is widthsPerLine = []; heightsPerLine = []; overallHeight = 0; size -= 2; context.font = size + "pt myText"; var widthOfSpace = context.measureText(" ").width; lines = []; var line = ""; var progressiveWidth = 0; for (var i = 0; i < words.length; ++i) { var word = words[i]; var heightToAdd = 0; if (word === "\n") { lines.push(line); if (line === "") //multiple newlines in a row heightToAdd = size * 0.5; else if (line === "-") //horizontal bar heightToAdd = size * 0.75; else if ((line.match(boldLinePatternWords) || line.match(boldLinePatternWordsSpecial)) && line.indexOf(" ") < 0) { //important line heightToAdd = boldSize * 1.433; var properFont = context.font; context.font = "bold " + boldSize + "pt myText"; //resizing up to 64 progressiveWidth = context.measureText(line).width; //=, not += context.font = properFont; } else if (line.match(iconWithNumbersPatternSingle) && !line.startsWith('+')) { heightToAdd = 275; //192 * 1.433 var properFont = context.font; context.font = "bold 192pt myText"; progressiveWidth = getWidthOfLineWithIconsReplacedWithSpaces(line); //=, not += context.font = properFont; } else //regular word heightToAdd = size * 1.433; line = ""; //start next line empty widthsPerLine.push(progressiveWidth); progressiveWidth = 0; } else { if (word.charAt(0) === "\xa0") { word = word.substring(1); } if (progressiveWidth + getWidthOfLineWithIconsReplacedWithSpaces(" " + word) > maxWidth) { lines.push(line + " "); line = word; heightToAdd = size * 1.433; widthsPerLine.push(progressiveWidth); progressiveWidth = getWidthOfLineWithIconsReplacedWithSpaces(word); } else { if (line.length) { line += " "; progressiveWidth += widthOfSpace; } line += word; var properFont = context.font; if (word.match(boldLinePatternWords) || word.match(boldLinePatternWordsSpecial)) //e.g. "+1 Action" context.font = "bold " + properFont; progressiveWidth += getWidthOfLineWithIconsReplacedWithSpaces(word); context.font = properFont; continue; } } overallHeight += heightToAdd; heightsPerLine.push(heightToAdd); } //overallHeight -= size*1.433; } while (overallHeight > maxHeight && size > 16); //can only shrink so far before giving up var y = yCenter - (overallHeight - size * 1.433) / 2; //var barHeight = size / 80 * 10; for (var i = 0; i < lines.length; ++i) { var line = lines[i]; if (line === "-") //horizontal bar context.fillRect(xCenter / 2, y - size * 0.375 - 5, xCenter, 10); else if (line.length) writeLineWithIconsReplacedWithSpaces(line, xCenter - widthsPerLine[i] / 2, y, size / 96, "myText", boldSize); //else empty line with nothing to draw y += heightsPerLine[i]; } context.fillStyle = "black"; } function writeIllustrationCredit(x, y, color, bold, size = 31) { var illustrationCredit = document.getElementById("credit").value; if (illustrationCredit) { context.font = bold + size + "pt myText"; context.fillStyle = color; context.fillText(illustrationCredit, x, y); context.fillStyle = "#000"; } } function writeCreatorCredit(x, y, color, bold, size = 31) { var creatorCredit = document.getElementById("creator").value; if (creatorCredit) { context.textAlign = "right"; context.font = bold + size + "pt myText"; context.fillStyle = color; context.fillText(creatorCredit, x, y); context.fillStyle = "#000"; } } if (!imagesLoaded) { imagesLoaded = (function () { for (var i = 0; i < images.length; ++i) if (!images[i].complete) { return false; } return true; })(); if (!imagesLoaded) { queueDraw(); return; } } //else ready to draw! canvases[0].parentNode.setAttribute("data-status", "Redrawing..."); // clear for (var i = 0; i < canvases.length; ++i) canvases[i].getContext("2d").clearRect(0, 0, canvases[i].width, canvases[i].height); var context; if (templateSize === 0 || templateSize === 2 || templateSize === 3) { context = canvases[0].getContext("2d"); } else if (templateSize === 1 || templateSize === 4) { context = canvases[1].getContext("2d"); } else { context = canvases[2].getContext("2d"); } //context.save(); // draw var picture = images[5]; var pictureX = document.getElementById("picture-x").value; var pictureY = document.getElementById("picture-y").value; var pictureZoom = document.getElementById("picture-zoom").value; var expansion = images[17]; var typeLine = document.getElementById("type").value; var heirloomLine = document.getElementById("type2").value; var previewLine = document.getElementById("preview").value; var priceLine = document.getElementById("price").value; var numberPriceIcons = (priceLine.match(new RegExp("[" + Object.keys(icons).join("") + "]", "g")) || []).length var isEachColorDark = [false, false]; for (var i = 0; i < 2; ++i) isEachColorDark[i] = (i == 1 && normalColorCurrentIndices[1] == 0) ? isEachColorDark[0] : (((normalColorCurrentIndices[i] >= normalColorCustomIndices[i]) ? recolorFactorList[i] : normalColorFactorLists[normalColorCurrentIndices[i] - i][1]).slice(0, 3).reduce(function getSum(total, num) { return total + parseFloat(num); }) <= 1.5); var differentIntensities = isEachColorDark[0] != isEachColorDark[1]; if (!(differentIntensities || parseInt(normalColorCurrentIndices[1]) == 0 || parseInt(normalColorCurrentIndices[0]) + 1 == parseInt(normalColorCurrentIndices[1]))) { document.getElementById('color2splitselector').removeAttribute("style"); } else { document.getElementById('color2splitselector').setAttribute("style", "display:none"); } function drawPicture(xCenter, yCenter, width, height) { if (picture.height) { var scale; if (picture.width / width > picture.height / height) { //size of area to draw picture to scale = height / picture.height; } else { scale = width / picture.width; } let sizeX = picture.width * scale * pictureZoom; let sizeY = picture.height * scale * pictureZoom; let spaceX = sizeX - width; let spaceY = sizeY - height; let moveX = parseFloat(pictureX) * spaceX / 2; let moveY = parseFloat(pictureY) * spaceY / 2; context.save(); context.translate(xCenter + moveX, yCenter + moveY); context.scale(scale * pictureZoom, scale * pictureZoom); context.drawImage(picture, picture.width / -2, picture.height / -2); context.restore(); } } function removeCorners(width, height, radius) { context.clearRect(0, 0, radius, radius); context.clearRect(width - radius, 0, radius, radius); context.clearRect(0, height - radius, radius, radius); context.clearRect(width - radius, height - radius, radius, radius); } function drawExpansionIcon(xCenter, yCenter, width, height) { if (expansion.height) { var scale; if (expansion.width / width < expansion.height / height) { //size of area to draw picture to scale = height / expansion.height; } else { scale = width / expansion.width; } context.save(); context.translate(xCenter, yCenter); context.scale(scale, scale); context.drawImage(expansion, expansion.width / -2, expansion.height / -2); context.restore(); } } if (templateSize == 0) { //card drawPicture(704, 706, 1150, 835); removeCorners(1403, 2151, 100); context.drawImage(getRecoloredImage(0, 0), 0, 0); //CardColorOne if (normalColorCurrentIndices[1] > 0) { //two colors are different let splitPosition = document.getElementById("color2split").value; if (splitPosition == 27) { context.drawImage(getRecoloredImage(1, 1), 0, 0); //CardColorTwo - Half context.drawImage(images[27], 0, 0); //CardColorThree } else { context.drawImage(getRecoloredImage(!differentIntensities ? splitPosition : 12, 1), 0, 0); //CardColorTwo } } context.drawImage(getRecoloredImage(2, 0, 6), 0, 0); //CardGray context.drawImage(getRecoloredImage(16, 0, 9), 0, 0); //CardBrown if (normalColorCurrentIndices[0] > 0 && !isEachColorDark[0] && normalColorCurrentIndices[1] == 0) //single (non-Action, non-Night) color context.drawImage(images[3], 44, 1094); //DescriptionFocus if (travellerTypesPattern.test(typeLine) || document.getElementById("traveller").checked) { context.save(); context.globalCompositeOperation = "luminosity"; if (isEachColorDark[0]) context.globalAlpha = 0.33; context.drawImage(images[4], 524, 1197); //Traveller context.restore(); } context.textAlign = "center"; context.textBaseline = "middle"; //context.font = "small-caps" + context.font; if (heirloomLine) { context.drawImage(images[13], 97, 1720); //Heirloom banner writeSingleLine(heirloomLine, 701, 1799, 1040, 58, "myText"); } if (isEachColorDark[1]) context.fillStyle = "white"; writeSingleLine(document.getElementById("title").value, 701, 215, previewLine ? 800 : 1180, 75); if (typeLine.split(" - ").length >= 4) { let types2 = typeLine.split(" - "); let types1 = types2.splice(0, Math.ceil(types2.length / 2)); let left = priceLine ? 750 + 65 * (numberPriceIcons - 1) : 701; let right = priceLine ? 890 - 65 * (numberPriceIcons - 1) : 1180; writeSingleLine(types1.join(" - ") + " -", left, 1922 - 26, right, 42); writeSingleLine(types2.join(" - "), left, 1922 + 26, right, 42); } else { if (expansion.height > 0 && expansion.width > 0) { let left = priceLine ? 730 + 65 * (numberPriceIcons - 1) : 701; let right = priceLine ? 800 - 65 * (numberPriceIcons - 1) : 900; writeSingleLine(typeLine, left, 1922, right, 64); } else { let left = priceLine ? 750 + 125 * (numberPriceIcons - 1) : 701; let right = priceLine ? 890 - 85 * (numberPriceIcons - 1) : 1180; writeSingleLine(typeLine, left, 1922, right, 64); } } if (priceLine) writeLineWithIconsReplacedWithSpaces(priceLine + " ", 153, 1940, 85 / 90, "mySpecials"); //adding a space confuses writeLineWithIconsReplacedWithSpaces into thinking this isn't a line that needs resizing if (previewLine) { writeSingleLine(previewLine += " ", 223, 210, 0, 0, "mySpecials"); writeSingleLine(previewLine, 1203, 210, 0, 0, "mySpecials"); } context.fillStyle = (isEachColorDark[0]) ? "white" : "black"; if (!heirloomLine) writeDescription("description", 701, 1500, 960, 660, 64); else writeDescription("description", 701, 1450, 960, 560, 64); writeIllustrationCredit(150, 2038, "white", ""); writeCreatorCredit(1253, 2038, "white", ""); drawExpansionIcon(1230, 1920, 80, 80); } else if (templateSize == 1) { //event/landscape drawPicture(1075, 584, 1887, 730); removeCorners(2151, 1403, 100); if (document.getElementById("trait").checked) { context.drawImage(getRecoloredImage(28, 0), 0, 0); //TraitColorOne if (heirloomLine) context.drawImage(images[14], 146, 832); //EventHeirloom context.drawImage(getRecoloredImage(29, 0, 6), 0, 0); //TraitUncoloredDetails context.drawImage(getRecoloredImage(15, 0, 9), 0, 0); //EventBar context.drawImage(getRecoloredImage(30, 0), 0, 0); //TraitColorSide context.drawImage(getRecoloredImage(31, 0, 6), 0, 0); //TraitUncoloredDetailsSide context.drawImage(getRecoloredImage(15, 0, 9), 0, 0); //EventBar } else { context.drawImage(getRecoloredImage(6, 0), 0, 0); //EventColorOne if (heirloomLine) context.drawImage(images[14], 146, 832); //EventHeirloom if (normalColorCurrentIndices[1] > 0) //two colors are different context.drawImage(getRecoloredImage(7, 1), 0, 0); //EventColorTwo context.drawImage(getRecoloredImage(8, 0, 6), 0, 0); //EventUncoloredDetails context.drawImage(getRecoloredImage(15, 0, 9), 0, 0); //EventBar } //no Traveller context.textAlign = "center"; context.textBaseline = "middle"; //context.font = "small-caps" + context.font; if (heirloomLine) writeSingleLine(heirloomLine, 1074, 900, 1600, 58, "myText"); if (isEachColorDark[0]) context.fillStyle = "white"; if (document.getElementById("trait").checked) { if (typeLine) { writeSingleLine(typeLine, 1075, 165, 780, 70); } context.save(); context.rotate(Math.PI * 3 / 2); writeSingleLine(document.getElementById("title").value, -700, 2030, 750, 70); context.restore(); context.save(); context.rotate(Math.PI / 2); writeSingleLine(document.getElementById("title").value, 700, -120, 750, 70); context.restore(); } else { writeSingleLine(document.getElementById("title").value, 1075, 165, 780, 70); if (typeLine) { context.save(); context.translate(1903, 240); context.rotate(45 * Math.PI / 180); context.scale(1, 0.8); //yes, the letters are shorter writeSingleLine(typeLine, 0, 0, 283, 64); context.restore(); } } if (priceLine) writeLineWithIconsReplacedWithSpaces(priceLine + " ", 130, 205, 85 / 90, "mySpecials"); //adding a space confuses writeLineWithIconsReplacedWithSpaces into thinking this isn't a line that needs resizing writeDescription("description", 1075, 1107, 1600, 283, 70); writeIllustrationCredit(181, 1272, "black", "bold "); writeCreatorCredit(1969, 1272, "black", "bold "); drawExpansionIcon(1930, 1190, 80, 80); } else if (templateSize == 2) { //double card drawPicture(704, 1075, 1150, 564); removeCorners(1403, 2151, 100); if (!recoloredImages[9]) recoloredImages[10] = false; context.drawImage(getRecoloredImage(9, 0), 0, 0); //DoubleColorOne if (!isEachColorDark[0]) context.drawImage(images[3], 44, 1330, images[3].width, images[3].height * 2 / 3); //DescriptionFocus context.save(); context.rotate(Math.PI); context.drawImage(getRecoloredImage(10, (normalColorCurrentIndices[1] > 0) ? 1 : 0), -1403, -2151); //DoubleColorOne again, but rotated if (!isEachColorDark[1]) context.drawImage(images[3], 44 - 1403, 1330 - 2151, images[3].width, images[3].height * 2 / 3); //DescriptionFocus context.restore(); context.drawImage(images[11], 0, 0); //DoubleUncoloredDetails //todo function drawHalfCard(t, l, p, d, colorID) { context.textAlign = "center"; context.textBaseline = "middle"; //context.font = "small-caps" + context.font; //writeSingleLine(document.getElementById(l).value, 701, 215, 1180, 75); var recolorFactors; if (normalColorCurrentIndices[colorID] >= normalColorCustomIndices[colorID]) recolorFactors = recolorFactorList[colorID]; else recolorFactors = normalColorFactorLists[normalColorCurrentIndices[colorID] - colorID][1]; context.save(); var title = document.getElementById(l).value; var size = 75 + 2; do { context.font = (size -= 2) + "pt myTitle"; } while (context.measureText(title).width > 750); context.textAlign = "left"; context.fillStyle = "rgb(" + Math.round(recolorFactors[0] * 224) + "," + Math.round(recolorFactors[1] * 224) + "," + Math.round(recolorFactors[2] * 224) + ")"; context.lineWidth = 15; if (isEachColorDark[colorID]) context.strokeStyle = "white"; context.strokeText(title, 150, 1287); context.fillText(title, 150, 1287); context.restore(); if (isEachColorDark[colorID]) context.fillStyle = "white"; writeSingleLine(t, p ? 750 : 701, 1922, p ? 890 : 1190, 64); if (p) writeLineWithIconsReplacedWithSpaces(p + " ", 153, 1940, 85 / 90, "mySpecials"); writeDescription(d, 701, 1600, 960, 460, 64); context.restore(); } context.save(); drawHalfCard(typeLine, "title", priceLine, "description", 0); context.save(); context.translate(1403, 2151); //bottom right corner context.rotate(Math.PI); shadowDistance = -shadowDistance; drawHalfCard(heirloomLine, "title2", previewLine, "description2", (normalColorCurrentIndices[1] > 0) ? 1 : 0); shadowDistance = -shadowDistance; context.textAlign = "left"; writeIllustrationCredit(150, 2038, "white", ""); writeCreatorCredit(1253, 2038, "white", ""); drawExpansionIcon(1230, 1920, 80, 80); } else if (templateSize == 3) { //base card drawPicture(704, 1075, 1150, 1898); removeCorners(1403, 2151, 100); context.drawImage(getRecoloredImage(20, 0), 0, 0); //CardColorOne context.drawImage(getRecoloredImage(21, 0, 6), 0, 0); //CardGray context.drawImage(getRecoloredImage(22, 0, 9), 0, 0); //CardBrown context.textAlign = "center"; context.textBaseline = "middle"; //context.font = "small-caps" + context.font; if (heirloomLine) { context.drawImage(images[13], 97, 1720); //Heirloom banner writeSingleLine(heirloomLine, 701, 1799, 1040, 58, "myText"); } if (isEachColorDark[1]) context.fillStyle = "white"; writeSingleLine(document.getElementById("title").value, 701, 215, previewLine ? 800 : 1180, 75); if (typeLine.split(" - ").length >= 4) { let types2 = typeLine.split(" - "); let types1 = types2.splice(0, Math.ceil(types2.length / 2)); writeSingleLine(types1.join(" - ") + " -", priceLine ? 750 : 701, 1945 - 26, priceLine ? 890 : 1180, 42); writeSingleLine(types2.join(" - "), priceLine ? 750 : 701, 1945 + 26, priceLine ? 890 : 1180, 42); } else { if (expansion.height > 0 && expansion.width > 0) { writeSingleLine(typeLine, priceLine ? 730 : 701, 1945, priceLine ? 800 : 900, 64); } else { writeSingleLine(typeLine, priceLine ? 750 : 701, 1945, priceLine ? 890 : 1180, 64); } } if (priceLine) writeLineWithIconsReplacedWithSpaces(priceLine + " ", 153, 1947, 85 / 90, "mySpecials"); //adding a space confuses writeLineWithIconsReplacedWithSpaces into thinking this isn't a line that needs resizing if (previewLine) { writeSingleLine(previewLine += " ", 223, 210, 0, 0, "mySpecials"); writeSingleLine(previewLine, 1203, 210, 0, 0, "mySpecials"); } context.fillStyle = (isEachColorDark[0]) ? "white" : "black"; if (!heirloomLine) writeDescription("description", 701, 1060, 960, 1500, 64); else writeDescription("description", 701, 1000, 960, 1400, 64); writeIllustrationCredit(165, 2045, "white", ""); writeCreatorCredit(1225, 2045, "white", ""); drawExpansionIcon(1230, 1945, 80, 80); } else if (templateSize == 4) { //pile marker drawPicture(1075, 702, 1250, 870); removeCorners(2151, 1403, 100); context.drawImage(getRecoloredImage(24, 0, 6), 0, 0); //CardGray context.drawImage(getRecoloredImage(23, 0), 0, 0); //CardColorOne context.textAlign = "center"; context.textBaseline = "middle"; context.save(); if (isEachColorDark[1]) context.fillStyle = "white"; context.rotate(Math.PI / 2); writeSingleLine(document.getElementById("title").value, 700, -1920, 500, 75); context.restore(); context.save(); if (isEachColorDark[1]) context.fillStyle = "white"; context.rotate(Math.PI * 3 / 2); writeSingleLine(document.getElementById("title").value, -700, 230, 500, 75); context.restore(); } else if (templateSize == 5) { //player mat drawPicture(464, 342, 928, 684); context.drawImage(getRecoloredImage(25, 0, 6), 0, 0); //MatBannerTop if (document.getElementById("description").value.trim().length > 0) context.drawImage(getRecoloredImage(26, 0, 6), 0, 0); //MatBannerBottom context.textAlign = "center"; context.textBaseline = "middle"; if (isEachColorDark[1]) context.fillStyle = "white"; writeSingleLine(document.getElementById("title").value, 464, 96, 490, 55); writeDescription("description", 464, 572, 740, 80, 44); writeIllustrationCredit(15, 660, "white", "", 16); writeCreatorCredit(913, 660, "white", "", 16); drawExpansionIcon(888, 40, 40, 40); } //finish up //context.restore(); updateURL(); document.getElementById("load-indicator").setAttribute("style", "display:none;"); canvases[0].parentNode.removeAttribute("data-status"); return; } // help function to load images CORS save // https://stackoverflow.com/a/43001137 function loadImgAsBase64(url, callback, maxWidth, maxHeight) { let canvas = document.createElement('CANVAS'); let img = document.createElement('img'); img.crossOrigin = "Anonymous"; if (url.substr(0, 11) != 'data:image/' && url.substr(0, 8) != 'file:///') { img.src = CORS_ANYWHERE_BASE_URL + url; } else { img.src = url; } img.onload = () => { let context = canvas.getContext('2d'); if (maxWidth > 0 && maxHeight > 0) { canvas.width = maxWidth; canvas.height = maxHeight; } else { canvas.height = img.height; canvas.width = img.width; } context.drawImage(img, 0, 0, canvas.width, canvas.height); let dataURL = canvas.toDataURL('image/png'); canvas = null; callback(dataURL); }; img.onerror = () => { useCORS = false; console.log("CORS loading of external resources deactivated"); callback(url); }; } // initialize stage var sources = [ "CardColorOne.png", "CardColorTwo.png", "CardGray.png", "DescriptionFocus.png", "Traveller.png", "", //illustration //5 "EventColorOne.png", "EventColorTwo.png", "EventBrown.png", "DoubleColorOne.png", "DoubleColorOne.png", //10 "DoubleUncoloredDetails.png", "CardColorTwoNight.png", "Heirloom.png", "EventHeirloom.png", "EventBrown2.png", //15 "CardBrown.png", "", //expansion "CardColorTwoSmall.png", "CardColorTwoBig.png", "BaseCardColorOne.png", //20 "BaseCardGray.png", "BaseCardBrown.png", "PileMarkerColorOne.png", "PileMarkerGrey.png", "MatBannerTop.png", //25 "MatBannerBottom.png", "CardColorThree.png", "TraitColorOne.png", "TraitBrown.png", "TraitColorOneSide.png", //30 "TraitBrownSide.png" //icons come afterwards ]; for (var i = 0; i < sources.length; i++) recoloredImages.push(false); var legend = document.getElementById("legend"); var numberFirstIcon = sources.length; for (key in icons) { var li = document.createElement("li"); li.textContent = ": " + icons[key][0]; var span = document.createElement("span"); span.classList.add("def"); span.textContent = key.replace("\\", ""); li.insertBefore(span, li.firstChild); legend.insertBefore(li, legend.firstChild); sources.push(icons[key][0] + ".png"); } for (var i = 0; i < sources.length; i++) { images.push(new Image()); images[i].crossOrigin = "Anonymous"; images[i].src = "card-resources/" + sources[i]; } var simpleOnChangeInputCheckboxIDs = ["traveller", "trait"]; var simpleOnChangeInputFieldIDs = ["title", "description", "type", "credit", "creator", "price", "preview", "type2", "color2split", "boldkeys", "picture-x", "picture-y", "picture-zoom"]; simpleOnChangeInputFieldIDs = simpleOnChangeInputFieldIDs.concat(simpleOnChangeInputCheckboxIDs); var simpleOnChangeButOnlyForSize2InputFieldIDs = ["title2", "description2"]; for (var i = 0; i < simpleOnChangeInputFieldIDs.length; ++i) { document.getElementById(simpleOnChangeInputFieldIDs[i]).onchange = queueDraw; if (i < simpleOnChangeButOnlyForSize2InputFieldIDs.length) document.getElementById(simpleOnChangeButOnlyForSize2InputFieldIDs[i]).onchange = queueDraw; } var recolorInputs = document.getElementsByName("recolor"); var alreadyNeededToDetermineCustomAccentColors = false; for (var i = 0; i < recolorInputs.length; ++i) recolorInputs[i].onchange = function (i) { return function () { var val = parseFloat(this.value); if (val !== NaN) { var imageID = Math.floor(i / 12); if (normalColorCurrentIndices[imageID] >= 10) { //potentially recoloring the supposedly Uncolored images recoloredImages[2] = false; recoloredImages[8] = false; recoloredImages[11] = false; recoloredImages[15] = false; recoloredImages[16] = false; recoloredImages[29] = false; recoloredImages[31] = false; } recoloredImages[imageID] = false; recoloredImages[imageID + 6] = false; recoloredImages[imageID + 9] = false; recoloredImages[12] = false; recoloredImages[18] = false; recoloredImages[19] = false; recoloredImages[20] = false; recoloredImages[23] = false; recoloredImages[28] = false; recoloredImages[30] = false; recolorFactorList[imageID][i % 12] = val; queueDraw(); } } }(i); function setImageSource(id, src) { images[id].src = src; images[id].crossOrigin = "Anonymous"; imagesLoaded = false; queueDraw(250); } function onChangeExternalImage(id, value, maxWidth, maxHeight) { let url = (sources[id] = value.trim()); if (url != "[local image]") { if (url.length > 0 && useCORS) { loadImgAsBase64(url, (dataURL) => { setImageSource(id, dataURL) }, maxWidth, maxHeight); } else { setImageSource(id, url); } } } function onUploadImage(id, file) { var reader = new FileReader(); reader.onload = () => { setImageSource(id, reader.result); console.log("image loaded"); }; reader.readAsDataURL(file); } if (document.getElementById("trait").checked) { document.body.classList.add("trait"); } document.getElementById("trait").addEventListener('change', () => { if (document.getElementById("trait").checked) { document.body.classList.add("trait"); } else { document.body.classList.remove("trait"); } }, false); try { // Image 5 = Main Picture document.getElementById("picture").onchange = function () { document.getElementById("picture-upload").value = ""; onChangeExternalImage(5, this.value); }; document.getElementById("picture-upload").onchange = (event) => { document.getElementById("picture").value = "[local image]"; onUploadImage(5, event.target.files[0]); }; } catch (err) {} try { // Image 17 = Expansion Icon document.getElementById("expansion").onchange = function () { document.getElementById("expansion-upload").value = ""; onChangeExternalImage(17, this.value); }; document.getElementById("expansion-upload").onchange = (event) => { document.getElementById("expansion").value = "[local image]"; onUploadImage(17, event.target.files[0]); }; } catch (err) {} try { //Last Icon = Custom Icon var customIcon = document.getElementById("custom-icon"); onChangeExternalImage(images.length - 1, customIcon.value, 156, 156); customIcon.onchange = function () { document.getElementById("custom-icon-upload").value = ""; onChangeExternalImage(images.length - 1, this.value, 156, 156); }; document.getElementById("custom-icon-upload").onchange = (event) => { customIcon.value = "[local image]"; onUploadImage(images.length - 1, event.target.files[0]); }; } catch (err) {} var genericCustomAccentColors = [ [0, 0, 0, 0, 0, 0, 1, 1, 1, 1.2, 0.8, 0.5], [0, 0, 0, 0, 0, 0, 0.9, 0.8, 0.7, 0.9, 0.8, 0.7] ]; for (i = 0; i < normalColorDropdowns.length; ++i) normalColorDropdowns[i].onchange = function (i) { return function () { if (normalColorCurrentIndices[i] >= 10 || this.selectedIndex >= 10) { //potentially recoloring the supposedly Uncolored images recoloredImages[2] = false; recoloredImages[8] = false; recoloredImages[11] = false; recoloredImages[15] = false; recoloredImages[16] = false; recoloredImages[29] = false; recoloredImages[31] = false; } normalColorCurrentIndices[i] = this.selectedIndex; recoloredImages[i] = false; recoloredImages[i + 6] = false; recoloredImages[i + 9] = false; recoloredImages[2] = false; recoloredImages[12] = false; recoloredImages[18] = false; recoloredImages[19] = false; recoloredImages[20] = false; recoloredImages[23] = false; recoloredImages[28] = false; recoloredImages[30] = false; var delta = normalColorCustomIndices[i] - this.selectedIndex; if (delta <= 0) this.nextElementSibling.removeAttribute("style"); else this.nextElementSibling.setAttribute("style", "display:none;"); if (delta === -1) { this.nextElementSibling.nextElementSibling.removeAttribute("style"); if (i === 0 && !alreadyNeededToDetermineCustomAccentColors) { alreadyNeededToDetermineCustomAccentColors = true; for (var j = 6; j < 12; ++j) recolorFactorList[0][j] = recolorInputs[j].value = genericCustomAccentColors[templateSize & 1][j]; } } else this.nextElementSibling.nextElementSibling.setAttribute("style", "display:none;"); queueDraw(1); } }(i); var templateSizeInputs = document.getElementsByName("size"); for (var i = 0; i < templateSizeInputs.length; ++i) templateSizeInputs[i].onchange = function (i) { return function () { templateSize = parseInt(this.value); document.body.className = this.id; document.body.classList.add("trait"); document.getElementById("load-indicator").removeAttribute("style"); queueDraw(250); } }(i); //ready to begin: load information from query parameters var query = getQueryParams(document.location.search); document.body.className = ""; for (var queryKey in query) { switch (queryKey) { case "color0": normalColorCurrentIndices[0] = normalColorDropdowns[0].selectedIndex = query[queryKey]; break; case "color1": normalColorCurrentIndices[1] = normalColorDropdowns[1].selectedIndex = query[queryKey]; break; case "size": var buttonElement = document.getElementsByName("size")[templateSize = parseInt(query[queryKey])]; document.body.classList.add(buttonElement.id); buttonElement.checked = true; break; case "traveller": var checkboxElement = document.getElementById(queryKey); checkboxElement.checked = query[queryKey] === 'true'; break; case "trait": var checkboxElement = document.getElementById(queryKey); checkboxElement.checked = query[queryKey] === 'true'; if (checkboxElement.checked === true) { document.body.classList.add(queryKey); } break; default: var matches = queryKey.match(/^c(\d)\.(\d)$/); if (matches) { var id = matches[1]; normalColorCurrentIndices[id] = normalColorDropdowns[id].selectedIndex = normalColorCustomIndices[id]; normalColorDropdowns[id].nextElementSibling.removeAttribute("style"); recolorFactorList[id][matches[2]] = recolorInputs[12 * id + parseInt(matches[2])].value = parseFloat(query[queryKey]); } else { matches = queryKey.match(/^c(\d)\.(\d)\.(\d)$/); if (matches) { alreadyNeededToDetermineCustomAccentColors = true; var id = matches[1]; normalColorCurrentIndices[id] = normalColorDropdowns[id].selectedIndex = normalColorCustomIndices[id] + 1; normalColorDropdowns[id].nextElementSibling.removeAttribute("style"); normalColorDropdowns[id].nextElementSibling.nextElementSibling.removeAttribute("style"); recolorFactorList[id][parseInt(matches[2]) * 3 + parseInt(matches[3])] = recolorInputs[12 * id + 3 * parseInt(matches[2]) + parseInt(matches[3])].value = parseFloat(query[queryKey]); } else { var el = document.getElementById(queryKey); if (el) el.value = query[queryKey]; } } break; } for (var i = 0; i < simpleOnChangeButOnlyForSize2InputFieldIDs.length; ++i) if (!document.getElementById(simpleOnChangeButOnlyForSize2InputFieldIDs[i]).value) document.getElementById(simpleOnChangeButOnlyForSize2InputFieldIDs[i]).value = document.getElementById(simpleOnChangeButOnlyForSize2InputFieldIDs[i].substr(0, simpleOnChangeButOnlyForSize2InputFieldIDs[i].length - 1)).value; } //set the illustration's Source properly and also call queueDraw. document.getElementById("picture").onchange(); document.getElementById("expansion").onchange(); document.getElementById("custom-icon").onchange(); //adjust page title function adjustPageTitle() { let cardTitle = document.getElementById("title").value.trim(); let creator = document.getElementById("creator").value.trim(); let pageDefaultTitle = "Dominion Card Image Generator"; document.title = cardTitle.length > 0 ? (pageDefaultTitle + " - " + cardTitle + " " + creator) : pageDefaultTitle; }; document.getElementById('title').addEventListener('change', adjustPageTitle, false); document.getElementById('creator').addEventListener('change', adjustPageTitle, false); adjustPageTitle(); //redraw after color switch document.getElementById('color-switch-button').addEventListener('click', switchColors, false); //pass parameters to original version to enable easy comparison document.getElementById('linkToOriginal').addEventListener('click', function (event) { event.preventDefault(); window.location.href = this.href + document.location.search; }, false); }