1""" 2Copyright (c) 2017 Wolthera van Hövell tot Westerflier <griffinvalley@gmail.com> 3 4This file is part of the Comics Project Management Tools(CPMT). 5 6CPMT is free software: you can redistribute it and/or modify 7it under the terms of the GNU General Public License as published by 8the Free Software Foundation, either version 3 of the License, or 9(at your option) any later version. 10 11CPMT is distributed in the hope that it will be useful, 12but WITHOUT ANY WARRANTY; without even the implied warranty of 13MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14GNU General Public License for more details. 15 16You should have received a copy of the GNU General Public License 17along with the CPMT. If not, see <http://www.gnu.org/licenses/>. 18""" 19 20""" 21An exporter that take the comicsConfig and uses it to generate several files. 22""" 23import sys 24from pathlib import Path 25import zipfile 26from xml.dom import minidom 27from xml.etree import ElementTree as ET 28import types 29import re 30from PyQt5.QtWidgets import QLabel, QProgressDialog, QMessageBox, qApp # For the progress dialog. 31from PyQt5.QtCore import QElapsedTimer, QLocale, Qt, QRectF, QPointF 32from PyQt5.QtGui import QImage, QTransform, QPainterPath, QFontMetrics, QFont 33from krita import * 34from . import exporters 35 36""" 37The sizesCalculator is a convenience class for interpretting the resize configuration 38from the export settings dialog. It is also used for batch resize. 39""" 40 41 42class sizesCalculator(): 43 44 def __init__(self): 45 pass 46 47 def get_scale_from_resize_config(self, config, listSizes): 48 listScaleTo = listSizes 49 oldWidth = listSizes[0] 50 oldHeight = listSizes[1] 51 oldXDPI = listSizes[2] 52 oldYDPI = listSizes[3] 53 if "Method" in config.keys(): 54 method = config["Method"] 55 if method == 0: 56 # percentage 57 percentage = config["Percentage"] / 100 58 listScaleTo[0] = round(oldWidth * percentage) 59 listScaleTo[1] = round(oldHeight * percentage) 60 if method == 1: 61 # dpi 62 DPI = config["DPI"] 63 listScaleTo[0] = round((oldWidth / oldXDPI) * DPI) 64 listScaleTo[1] = round((oldHeight / oldYDPI) * DPI) 65 listScaleTo[2] = DPI 66 listScaleTo[3] = DPI 67 if method == 2: 68 # maximum width 69 width = config["Width"] 70 listScaleTo[0] = width 71 listScaleTo[1] = round((oldHeight / oldWidth) * width) 72 if method == 3: 73 # maximum height 74 height = config["Height"] 75 listScaleTo[1] = height 76 listScaleTo[0] = round((oldWidth / oldHeight) * height) 77 return listScaleTo 78 79 80""" 81The comicsExporter is a class that batch exports to all the requested formats. 82Make it, set_config with the right data, and then call up "export". 83 84The majority of the functions are meta-data encoding functions. 85""" 86 87 88class comicsExporter(): 89 acbfLocation = str() 90 acbfPageData = [] 91 cometLocation = str() 92 comicRackInfo = str() 93 pagesLocationList = {} 94 95 # set of keys used to define specific export behaviour for this page. 96 pageKeys = ["acbf_title", "acbf_none", "acbf_fade", "acbf_blend", "acbf_horizontal", "acbf_vertical", "epub_spread"] 97 98 def __init__(self): 99 pass 100 101 """ 102 The configuration of the exporter. 103 104 @param config: A dictionary containing all the config. 105 106 @param projectUrl: the main location of the project folder. 107 """ 108 109 def set_config(self, config, projectURL): 110 self.configDictionary = config 111 self.projectURL = projectURL 112 self.pagesLocationList = {} 113 self.acbfLocation = str() 114 self.acbfPageData = [] 115 self.cometLocation = str() 116 self.comicRackInfo = str() 117 118 """ 119 Export everything according to config and get yourself a coffee. 120 This won't work if the config hasn't been set. 121 """ 122 123 def export(self): 124 export_success = False 125 126 path = Path(self.projectURL) 127 if path.exists(): 128 # Make a meta-data folder so we keep the export folder nice and clean. 129 exportPath = path / self.configDictionary["exportLocation"] 130 if Path(exportPath / "metadata").exists() is False: 131 Path(exportPath / "metadata").mkdir() 132 133 # Get to which formats to export, and set the sizeslist. 134 lengthProcess = len(self.configDictionary["pages"]) 135 sizesList = {} 136 if "CBZ" in self.configDictionary.keys(): 137 if self.configDictionary["CBZactive"]: 138 lengthProcess += 5 139 sizesList["CBZ"] = self.configDictionary["CBZ"] 140 if "EPUB" in self.configDictionary.keys(): 141 if self.configDictionary["EPUBactive"]: 142 lengthProcess += 1 143 sizesList["EPUB"] = self.configDictionary["EPUB"] 144 if "TIFF" in self.configDictionary.keys(): 145 if self.configDictionary["TIFFactive"]: 146 sizesList["TIFF"] = self.configDictionary["TIFF"] 147 # Export the pngs according to the sizeslist. 148 # Create a progress dialog. 149 self.progress = QProgressDialog(i18n("Preparing export."), str(), 0, lengthProcess) 150 self.progress.setWindowTitle(i18n("Exporting Comic...")) 151 self.progress.setCancelButton(None) 152 self.timer = QElapsedTimer() 153 self.timer.start() 154 self.progress.show() 155 qApp.processEvents() 156 export_success = self.save_out_pngs(sizesList) 157 158 # Export acbf metadata. 159 if export_success: 160 if "CBZ" in sizesList.keys(): 161 title = self.configDictionary["projectName"] 162 if "title" in self.configDictionary.keys(): 163 title = str(self.configDictionary["title"]).replace(" ", "_") 164 165 self.acbfLocation = str(exportPath / "metadata" / str(title + ".acbf")) 166 167 locationStandAlone = str(exportPath / str(title + ".acbf")) 168 self.progress.setLabelText(i18n("Saving out ACBF and\nACBF standalone")) 169 self.progress.setValue(self.progress.value()+2) 170 export_success = exporters.ACBF.write_xml(self.configDictionary, self.acbfPageData, self.pagesLocationList["CBZ"], self.acbfLocation, locationStandAlone, self.projectURL) 171 print("CPMT: Exported to ACBF", export_success) 172 173 # Export and package CBZ and Epub. 174 if export_success: 175 if "CBZ" in sizesList.keys(): 176 export_success = self.export_to_cbz(exportPath) 177 print("CPMT: Exported to CBZ", export_success) 178 if "EPUB" in sizesList.keys(): 179 self.progress.setLabelText(i18n("Saving out EPUB")) 180 self.progress.setValue(self.progress.value()+1) 181 export_success = exporters.EPUB.export(self.configDictionary, self.projectURL, self.pagesLocationList["EPUB"], self.acbfPageData) 182 print("CPMT: Exported to EPUB", export_success) 183 else: 184 QMessageBox.warning(None, i18n("Export not Possible"), i18n("Nothing to export, URL not set."), QMessageBox.Ok) 185 print("CPMT: Nothing to export, url not set.") 186 187 return export_success 188 189 """ 190 This calls up all the functions necessary for making a cbz. 191 """ 192 193 def export_to_cbz(self, exportPath): 194 title = self.configDictionary["projectName"] 195 if "title" in self.configDictionary.keys(): 196 title = str(self.configDictionary["title"]).replace(" ", "_") 197 self.progress.setLabelText(i18n("Saving out CoMet\nmetadata file")) 198 self.progress.setValue(self.progress.value()+1) 199 self.cometLocation = str(exportPath / "metadata" / str(title + " CoMet.xml")) 200 export_success = exporters.CoMet.write_xml(self.configDictionary, self.pagesLocationList["CBZ"], self.cometLocation) 201 self.comicRackInfo = str(exportPath / "metadata" / "ComicInfo.xml") 202 self.progress.setLabelText(i18n("Saving out Comicrack\nmetadata file")) 203 self.progress.setValue(self.progress.value()+1) 204 export_success = exporters.comic_rack_xml.write_xml(self.configDictionary, self.pagesLocationList["CBZ"], self.comicRackInfo) 205 self.package_cbz(exportPath) 206 return export_success 207 208 def save_out_pngs(self, sizesList): 209 # A small fix to ensure crop to guides is set. 210 if "cropToGuides" not in self.configDictionary.keys(): 211 self.configDictionary["cropToGuides"] = False 212 213 # Check if we have pages at all... 214 if "pages" in self.configDictionary.keys(): 215 216 # Check if there's export methods, and if so make sure the appropriate dictionaries are initialised. 217 if len(sizesList.keys()) < 1: 218 QMessageBox.warning(None, i18n("Export not Possible"), i18n("Export failed because there's no export settings configured."), QMessageBox.Ok) 219 print("CPMT: Export failed because there's no export methods set.") 220 return False 221 else: 222 for key in sizesList.keys(): 223 self.pagesLocationList[key] = [] 224 225 # Get the appropriate paths. 226 path = Path(self.projectURL) 227 exportPath = path / self.configDictionary["exportLocation"] 228 pagesList = self.configDictionary["pages"] 229 fileName = str(exportPath) 230 231 """ 232 Mini function to handle the setup of this string. 233 """ 234 def timeString(timePassed, timeEstimated): 235 return str(i18n("Time passed: {passedString}\n Estimated: {estimated}")).format(passedString=timePassed, estimated=timeEstimated) 236 237 for p in range(0, len(pagesList)): 238 pagesDone = str(i18n("{pages} of {pagesTotal} done.")).format(pages=p, pagesTotal=len(pagesList)) 239 240 # Update the label in the progress dialog. 241 self.progress.setValue(p) 242 timePassed = self.timer.elapsed() 243 if p > 0: 244 timeEstimated = (len(pagesList) - p) * (timePassed / p) 245 estimatedString = self.parseTime(timeEstimated) 246 else: 247 estimatedString = str(u"\u221E") 248 passedString = self.parseTime(timePassed) 249 self.progress.setLabelText("\n".join([pagesDone, timeString(passedString, estimatedString), i18n("Opening next page")])) 250 qApp.processEvents() 251 # Get the appropriate url and open the page. 252 url = str(Path(self.projectURL) / pagesList[p]) 253 page = Application.openDocument(url) 254 page.waitForDone() 255 256 # Update the progress bar a little 257 self.progress.setLabelText("\n".join([pagesDone, timeString(self.parseTime(self.timer.elapsed()), estimatedString), i18n("Cleaning up page")])) 258 259 # remove layers and flatten. 260 labelList = self.configDictionary["labelsToRemove"] 261 panelsAndText = [] 262 263 # These three lines are what is causing the page not to close. 264 root = page.rootNode() 265 self.getPanelsAndText(root, panelsAndText) 266 self.removeLayers(labelList, root) 267 page.refreshProjection() 268 # We'll need the offset and scale for aligning the panels and text correctly. We're getting this from the CBZ 269 270 pageData = {} 271 pageData["vector"] = panelsAndText 272 tree = ET.fromstring(page.documentInfo()) 273 pageData["title"] = page.name() 274 calligra = "{http://www.calligra.org/DTD/document-info}" 275 about = tree.find(calligra + "about") 276 keywords = about.find(calligra + "keyword") 277 keys = str(keywords.text).split(",") 278 pKeys = [] 279 for key in keys: 280 if key in self.pageKeys: 281 pKeys.append(key) 282 pageData["keys"] = pKeys 283 page.flatten() 284 page.waitForDone() 285 batchsave = Application.batchmode() 286 Application.setBatchmode(True) 287 # Start making the format specific copy. 288 for key in sizesList.keys(): 289 290 # Update the progress bar a little 291 self.progress.setLabelText("\n".join([pagesDone, timeString(self.parseTime(self.timer.elapsed()), estimatedString), str(i18n("Exporting for {key}")).format(key=key)])) 292 293 w = sizesList[key] 294 # copy over data 295 projection = page.clone() 296 projection.setBatchmode(True) 297 # Crop. Cropping per guide only happens if said guides have been found. 298 if w["Crop"] is True: 299 listHGuides = [] 300 listHGuides = page.horizontalGuides() 301 listHGuides.sort() 302 for i in range(len(listHGuides) - 1, 0, -1): 303 if listHGuides[i] < 0 or listHGuides[i] > page.height(): 304 listHGuides.pop(i) 305 listVGuides = page.verticalGuides() 306 listVGuides.sort() 307 for i in range(len(listVGuides) - 1, 0, -1): 308 if listVGuides[i] < 0 or listVGuides[i] > page.width(): 309 listVGuides.pop(i) 310 if self.configDictionary["cropToGuides"] and len(listVGuides) > 1: 311 cropx = listVGuides[0] 312 cropw = listVGuides[-1] - cropx 313 else: 314 cropx = self.configDictionary["cropLeft"] 315 cropw = page.width() - self.configDictionary["cropRight"] - cropx 316 if self.configDictionary["cropToGuides"] and len(listHGuides) > 1: 317 cropy = listHGuides[0] 318 croph = listHGuides[-1] - cropy 319 else: 320 cropy = self.configDictionary["cropTop"] 321 croph = page.height() - self.configDictionary["cropBottom"] - cropy 322 projection.crop(cropx, cropy, cropw, croph) 323 projection.waitForDone() 324 qApp.processEvents() 325 # resize appropriately 326 else: 327 cropx = 0 328 cropy = 0 329 res = page.resolution() 330 listScales = [projection.width(), projection.height(), res, res] 331 projectionOldSize = [projection.width(), projection.height()] 332 sizesCalc = sizesCalculator() 333 listScales = sizesCalc.get_scale_from_resize_config(config=w, listSizes=listScales) 334 projection.scaleImage(listScales[0], listScales[1], listScales[2], listScales[3], "bicubic") 335 projection.waitForDone() 336 qApp.processEvents() 337 # png, gif and other webformats should probably be in 8bit srgb at maximum. 338 if key != "TIFF": 339 if (projection.colorModel() != "RGBA" and projection.colorModel() != "GRAYA") or projection.colorDepth() != "U8": 340 projection.setColorSpace("RGBA", "U8", "sRGB built-in") 341 else: 342 # Tiff on the other hand can handle all the colormodels, but can only handle integer bit depths. 343 # Tiff is intended for print output, and 16 bit integer will be sufficient. 344 if projection.colorDepth() != "U8" or projection.colorDepth() != "U16": 345 projection.setColorSpace(page.colorModel(), "U16", page.colorProfile()) 346 # save 347 # Make sure the folder name for this export exists. It'll allow us to keep the 348 # export folders nice and clean. 349 folderName = str(key + "-" + w["FileType"]) 350 if Path(exportPath / folderName).exists() is False: 351 Path.mkdir(exportPath / folderName) 352 # Get a nice and descriptive fle name. 353 fn = str(Path(exportPath / folderName) / str("page_" + format(p, "03d") + "_" + str(listScales[0]) + "x" + str(listScales[1]) + "." + w["FileType"])) 354 # Finally save and add the page to a list of pages. This will make it easy for the packaging function to 355 # find the pages and store them. 356 projection.exportImage(fn, InfoObject()) 357 projection.waitForDone() 358 qApp.processEvents() 359 if key == "CBZ" or key == "EPUB": 360 transform = {} 361 transform["offsetX"] = cropx 362 transform["offsetY"] = cropy 363 transform["resDiff"] = page.resolution() / 72 364 transform["scaleWidth"] = projection.width() / projectionOldSize[0] 365 transform["scaleHeight"] = projection.height() / projectionOldSize[1] 366 pageData["transform"] = transform 367 self.pagesLocationList[key].append(fn) 368 projection.close() 369 self.acbfPageData.append(pageData) 370 page.close() 371 self.progress.setValue(len(pagesList)) 372 Application.setBatchmode(batchsave) 373 # TODO: Check what or whether memory leaks are still caused and otherwise remove the entry below. 374 print("CPMT: Export has finished. If there are memory leaks, they are caused by file layers.") 375 return True 376 print("CPMT: Export not happening because there aren't any pages.") 377 QMessageBox.warning(None, i18n("Export not Possible"), i18n("Export not happening because there are no pages."), QMessageBox.Ok) 378 return False 379 380 """ 381 Function to get the panel and text data. 382 """ 383 384 def getPanelsAndText(self, node, list): 385 textLayersToSearch = ["text"] 386 panelLayersToSearch = ["panels"] 387 if "textLayerNames" in self.configDictionary.keys(): 388 textLayersToSearch = self.configDictionary["textLayerNames"] 389 if "panelLayerNames" in self.configDictionary.keys(): 390 panelLayersToSearch = self.configDictionary["panelLayerNames"] 391 if node.type() == "vectorlayer": 392 for name in panelLayersToSearch: 393 if str(name).lower() in str(node.name()).lower(): 394 for shape in node.shapes(): 395 if (shape.type() == "groupshape"): 396 self.getPanelsAndTextVector(shape, list) 397 else: 398 self.handleShapeDescription(shape, list) 399 for name in textLayersToSearch: 400 if str(name).lower() in str(node.name()).lower(): 401 for shape in node.shapes(): 402 if (shape.type() == "groupshape"): 403 self.getPanelsAndTextVector(shape, list, True) 404 else: 405 self.handleShapeDescription(shape, list, True) 406 else: 407 if node.childNodes(): 408 for child in node.childNodes(): 409 self.getPanelsAndText(node=child, list=list) 410 411 def parseTime(self, time = 0): 412 timeList = [] 413 timeList.append(str(int(time / 60000))) 414 timeList.append(format(int((time%60000) / 1000), "02d")) 415 timeList.append(format(int(time % 1000), "03d")) 416 return ":".join(timeList) 417 """ 418 Function to get the panel and text data from a group shape 419 """ 420 421 def getPanelsAndTextVector(self, group, list, textOnly=False): 422 for shape in group.shapes(): 423 if (shape.type() == "groupshape"): 424 self.getPanelsAndTextVector(shape, list, textOnly) 425 else: 426 self.handleShapeDescription(shape, list, textOnly) 427 """ 428 Function to get text and panels in a format that acbf will accept 429 """ 430 431 def handleShapeDescription(self, shape, list, textOnly=False): 432 if (shape.type() != "KoSvgTextShapeID" and textOnly is True): 433 return 434 shapeDesc = {} 435 shapeDesc["name"] = shape.name() 436 rect = shape.boundingBox() 437 listOfPoints = [rect.topLeft(), rect.topRight(), rect.bottomRight(), rect.bottomLeft()] 438 shapeDoc = minidom.parseString(shape.toSvg()) 439 docElem = shapeDoc.documentElement 440 svgRegExp = re.compile('[MLCSQHVATmlzcqshva]\d+\.?\d* \d+\.?\d*') 441 transform = docElem.getAttribute("transform") 442 coord = [] 443 adjust = QTransform() 444 # TODO: If we get global transform api, use that instead of parsing manually. 445 if "translate" in transform: 446 transform = transform.replace('translate(', '') 447 for c in transform[:-1].split(" "): 448 if "," in c: 449 c = c.replace(",", "") 450 coord.append(float(c)) 451 if len(coord) < 2: 452 coord.append(coord[0]) 453 adjust = QTransform(1, 0, 0, 1, coord[0], coord[1]) 454 if "matrix" in transform: 455 transform = transform.replace('matrix(', '') 456 for c in transform[:-1].split(" "): 457 if "," in c: 458 c = c.replace(",", "") 459 coord.append(float(c)) 460 adjust = QTransform(coord[0], coord[1], coord[2], coord[3], coord[4], coord[5]) 461 path = QPainterPath() 462 if docElem.localName == "path": 463 dVal = docElem.getAttribute("d") 464 listOfSvgStrings = [" "] 465 listOfSvgStrings = svgRegExp.findall(dVal) 466 if listOfSvgStrings: 467 listOfPoints = [] 468 for l in listOfSvgStrings: 469 line = l[1:] 470 coordinates = line.split(" ") 471 if len(coordinates) < 2: 472 coordinates.append(coordinates[0]) 473 x = float(coordinates[-2]) 474 y = float(coordinates[-1]) 475 offset = QPointF() 476 if l.islower(): 477 offset = listOfPoints[0] 478 if l.lower().startswith("m"): 479 path.moveTo(QPointF(x, y) + offset) 480 elif l.lower().startswith("h"): 481 y = listOfPoints[-1].y() 482 path.lineTo(QPointF(x, y) + offset) 483 elif l.lower().startswith("v"): 484 x = listOfPoints[-1].x() 485 path.lineTo(QPointF(x, y) + offset) 486 elif l.lower().startswith("c"): 487 path.cubicTo(coordinates[0], coordinates[1], coordinates[2], coordinates[3], x, y) 488 else: 489 path.lineTo(QPointF(x, y) + offset) 490 path.setFillRule(Qt.WindingFill) 491 for polygon in path.simplified().toSubpathPolygons(adjust): 492 for point in polygon: 493 listOfPoints.append(point) 494 elif docElem.localName == "rect": 495 listOfPoints = [] 496 if (docElem.hasAttribute("x")): 497 x = float(docElem.getAttribute("x")) 498 else: 499 x = 0 500 if (docElem.hasAttribute("y")): 501 y = float(docElem.getAttribute("y")) 502 else: 503 y = 0 504 w = float(docElem.getAttribute("width")) 505 h = float(docElem.getAttribute("height")) 506 path.addRect(QRectF(x, y, w, h)) 507 for point in path.toFillPolygon(adjust): 508 listOfPoints.append(point) 509 elif docElem.localName == "ellipse": 510 listOfPoints = [] 511 if (docElem.hasAttribute("cx")): 512 x = float(docElem.getAttribute("cx")) 513 else: 514 x = 0 515 if (docElem.hasAttribute("cy")): 516 y = float(docElem.getAttribute("cy")) 517 else: 518 y = 0 519 ry = float(docElem.getAttribute("ry")) 520 rx = float(docElem.getAttribute("rx")) 521 path.addEllipse(QPointF(x, y), rx, ry) 522 for point in path.toFillPolygon(adjust): 523 listOfPoints.append(point) 524 elif docElem.localName == "text": 525 # NOTE: This only works for horizontal preformated text. Vertical text needs a different 526 # ordering of the rects, and wraparound should try to take the shape it is wrapped in. 527 family = "sans-serif" 528 if docElem.hasAttribute("font-family"): 529 family = docElem.getAttribute("font-family") 530 size = "11" 531 if docElem.hasAttribute("font-size"): 532 size = docElem.getAttribute("font-size") 533 multilineText = True 534 for el in docElem.childNodes: 535 if el.nodeType == minidom.Node.TEXT_NODE: 536 multilineText = False 537 if multilineText: 538 listOfPoints = [] 539 listOfRects = [] 540 541 # First we collect all the possible line-rects. 542 for el in docElem.childNodes: 543 if docElem.hasAttribute("font-family"): 544 family = docElem.getAttribute("font-family") 545 if docElem.hasAttribute("font-size"): 546 size = docElem.getAttribute("font-size") 547 fontsize = int(size) 548 font = QFont(family, fontsize) 549 string = el.toxml() 550 string = re.sub("\<.*?\>", " ", string) 551 string = string.replace(" ", " ") 552 width = min(QFontMetrics(font).width(string.strip()), rect.width()) 553 height = QFontMetrics(font).height() 554 anchor = "start" 555 if docElem.hasAttribute("text-anchor"): 556 anchor = docElem.getAttribute("text-anchor") 557 top = rect.top() 558 if len(listOfRects)>0: 559 top = listOfRects[-1].bottom() 560 if anchor == "start": 561 spanRect = QRectF(rect.left(), top, width, height) 562 listOfRects.append(spanRect) 563 elif anchor == "end": 564 spanRect = QRectF(rect.right()-width, top, width, height) 565 listOfRects.append(spanRect) 566 else: 567 # Middle 568 spanRect = QRectF(rect.center().x()-(width*0.5), top, width, height) 569 listOfRects.append(spanRect) 570 # Now we have all the rects, we can check each and draw a 571 # polygon around them. 572 heightAdjust = (rect.height()-(listOfRects[-1].bottom()-rect.top()))/len(listOfRects) 573 for i in range(len(listOfRects)): 574 span = listOfRects[i] 575 addtionalHeight = i*heightAdjust 576 if i == 0: 577 listOfPoints.append(span.topLeft()) 578 listOfPoints.append(span.topRight()) 579 else: 580 if listOfRects[i-1].width()< span.width(): 581 listOfPoints.append(QPointF(span.right(), span.top()+addtionalHeight)) 582 listOfPoints.insert(0, QPointF(span.left(), span.top()+addtionalHeight)) 583 else: 584 bottom = listOfRects[i-1].bottom()+addtionalHeight-heightAdjust 585 listOfPoints.append(QPointF(listOfRects[i-1].right(), bottom)) 586 listOfPoints.insert(0, QPointF(listOfRects[i-1].left(), bottom)) 587 listOfPoints.append(QPointF(span.right(), rect.bottom())) 588 listOfPoints.insert(0, QPointF(span.left(), rect.bottom())) 589 path = QPainterPath() 590 path.moveTo(listOfPoints[0]) 591 for p in range(1, len(listOfPoints)): 592 path.lineTo(listOfPoints[p]) 593 path.closeSubpath() 594 listOfPoints = [] 595 for point in path.toFillPolygon(adjust): 596 listOfPoints.append(point) 597 shapeDesc["boundingBox"] = listOfPoints 598 if (shape.type() == "KoSvgTextShapeID" and textOnly is True): 599 shapeDesc["text"] = shape.toSvg() 600 list.append(shapeDesc) 601 602 """ 603 Function to remove layers when they have the given labels. 604 605 If not, but the node does have children, check those too. 606 """ 607 608 def removeLayers(self, labels, node): 609 if node.colorLabel() in labels: 610 node.remove() 611 else: 612 if node.childNodes(): 613 for child in node.childNodes(): 614 self.removeLayers(labels, node=child) 615 616 """ 617 package cbz puts all the meta-data and relevant files into an zip file ending with ".cbz" 618 """ 619 620 def package_cbz(self, exportPath): 621 622 # Use the project name if there's no title to avoid sillyness with unnamed zipfiles. 623 title = self.configDictionary["projectName"] 624 if "title" in self.configDictionary.keys(): 625 title = str(self.configDictionary["title"]).replace(" ", "_") 626 627 # Get the appropriate path. 628 url = str(exportPath / str(title + ".cbz")) 629 630 # Create a zip file. 631 cbzArchive = zipfile.ZipFile(url, mode="w", compression=zipfile.ZIP_STORED) 632 633 # Add all the meta data files. 634 cbzArchive.write(self.acbfLocation, Path(self.acbfLocation).name) 635 cbzArchive.write(self.cometLocation, Path(self.cometLocation).name) 636 cbzArchive.write(self.comicRackInfo, Path(self.comicRackInfo).name) 637 comic_book_info_json_dump = str() 638 self.progress.setLabelText(i18n("Saving out Comicbook\ninfo metadata file")) 639 self.progress.setValue(self.progress.value()+1) 640 comic_book_info_json_dump = exporters.comic_book_info.writeJson(self.configDictionary) 641 cbzArchive.comment = comic_book_info_json_dump.encode("utf-8") 642 643 # Add the pages. 644 if "CBZ" in self.pagesLocationList.keys(): 645 for page in self.pagesLocationList["CBZ"]: 646 if (Path(page).exists()): 647 cbzArchive.write(page, Path(page).name) 648 self.progress.setLabelText(i18n("Packaging CBZ")) 649 self.progress.setValue(self.progress.value()+1) 650 # Close the zip file when done. 651 cbzArchive.close() 652