Krita Source Code Documentation
Loading...
Searching...
No Matches
comics_exporter.py
Go to the documentation of this file.
1"""
2SPDX-FileCopyrightText: 2017 Wolthera van Hövell tot Westerflier <griffinvalley@gmail.com>
3
4This file is part of the Comics Project Management Tools(CPMT).
5
6SPDX-License-Identifier: GPL-3.0-or-later
7"""
8
9"""
10An exporter that take the comicsConfig and uses it to generate several files.
11"""
12from pathlib import Path
13import zipfile
14from xml.etree import ElementTree as ET
15try:
16 from PyQt6.QtWidgets import QProgressDialog, QMessageBox, QApplication # For the progress dialog.
17 from PyQt6.QtCore import QElapsedTimer
18except:
19 from PyQt5.QtWidgets import QProgressDialog, QMessageBox, QApplication # For the progress dialog.
20 from PyQt5.QtCore import QElapsedTimer
21from krita import InfoObject
22from builtins import i18n, Application
23from . import exporters
24
25"""
26The sizesCalculator is a convenience class for interpreting the resize configuration
27from the export settings dialog. It is also used for batch resize.
28"""
29
30
32
33 def __init__(self):
34 pass
35
36 def get_scale_from_resize_config(self, config, listSizes):
37 listScaleTo = listSizes
38 oldWidth = listSizes[0]
39 oldHeight = listSizes[1]
40 oldXDPI = listSizes[2]
41 oldYDPI = listSizes[3]
42 if "Method" in config.keys():
43 method = config["Method"]
44 if method == 0:
45 # percentage
46 percentage = config["Percentage"] / 100
47 listScaleTo[0] = round(oldWidth * percentage)
48 listScaleTo[1] = round(oldHeight * percentage)
49 if method == 1:
50 # dpi
51 DPI = config["DPI"]
52 listScaleTo[0] = round((oldWidth / oldXDPI) * DPI)
53 listScaleTo[1] = round((oldHeight / oldYDPI) * DPI)
54 listScaleTo[2] = DPI
55 listScaleTo[3] = DPI
56 if method == 2:
57 # maximum width
58 width = config["Width"]
59 listScaleTo[0] = width
60 listScaleTo[1] = round((oldHeight / oldWidth) * width)
61 if method == 3:
62 # maximum height
63 height = config["Height"]
64 listScaleTo[1] = height
65 listScaleTo[0] = round((oldWidth / oldHeight) * height)
66 return listScaleTo
67
68
69"""
70The comicsExporter is a class that batch exports to all the requested formats.
71Make it, set_config with the right data, and then call up "export".
72
73The majority of the functions are meta-data encoding functions.
74"""
75
76
78 acbfLocation = str()
79 acbfPageData = []
80 cometLocation = str()
81 comicRackInfo = str()
82 pagesLocationList = {}
83
84 # set of keys used to define specific export behaviour for this page.
85 pageKeys = ["acbf_title", "acbf_none", "acbf_fade", "acbf_blend", "acbf_horizontal", "acbf_vertical", "epub_spread"]
86
87 def __init__(self):
88 pass
89
90 """
91 The configuration of the exporter.
92
93 @param config: A dictionary containing all the config.
94
95 @param projectUrl: the main location of the project folder.
96 """
97
98 def set_config(self, config, projectURL):
99 self.configDictionary = config
100 self.projectURL = projectURL
102 self.acbfLocation = str()
104 self.cometLocation = str()
105 self.comicRackInfo = str()
106
107 """
108 Export everything according to config and get yourself a coffee.
109 This won't work if the config hasn't been set.
110 """
111
112 def export(self):
113 export_success = False
114
115 path = Path(self.projectURL)
116 if path.exists():
117 # Make a meta-data folder so we keep the export folder nice and clean.
118 exportPath = path / self.configDictionary["exportLocation"]
119 if Path(exportPath / "metadata").exists() is False:
120 Path(exportPath / "metadata").mkdir()
121
122 # Get to which formats to export, and set the sizeslist.
123 lengthProcess = len(self.configDictionary["pages"])
124 sizesList = {}
125 if "CBZ" in self.configDictionary.keys():
126 if self.configDictionary["CBZactive"]:
127 lengthProcess += 5
128 sizesList["CBZ"] = self.configDictionary["CBZ"]
129 if "EPUB" in self.configDictionary.keys():
130 if self.configDictionary["EPUBactive"]:
131 lengthProcess += 1
132 sizesList["EPUB"] = self.configDictionary["EPUB"]
133 if "TIFF" in self.configDictionary.keys():
134 if self.configDictionary["TIFFactive"]:
135 sizesList["TIFF"] = self.configDictionary["TIFF"]
136 # Export the pngs according to the sizeslist.
137 # Create a progress dialog.
138 self.progress = QProgressDialog(i18n("Preparing export."), str(), 0, lengthProcess)
139 self.progress.setWindowTitle(i18n("Exporting Comic..."))
140 self.progress.setCancelButton(None)
141 self.timer = QElapsedTimer()
142 self.timer.start()
143 self.progress.show()
144 QApplication.instance().processEvents()
145 export_success = self.save_out_pngs(sizesList)
146
147 # Export acbf metadata.
148 if export_success:
149 if "CBZ" in sizesList.keys():
150 title = self.configDictionary["projectName"]
151 if "title" in self.configDictionary.keys():
152 title = str(self.configDictionary["title"]).replace(" ", "_")
153
154 self.acbfLocation = str(exportPath / "metadata" / str(title + ".acbf"))
155
156 locationStandAlone = str(exportPath / str(title + ".acbf"))
157 self.progress.setLabelText(i18n("Saving out ACBF and\nACBF standalone"))
158 self.progress.setValue(self.progress.value()+2)
159 export_success = exporters.ACBF.write_xml(self.configDictionary, self.acbfPageDataacbfPageData, self.pagesLocationListpagesLocationList["CBZ"], self.acbfLocation, locationStandAlone, self.projectURL)
160 print("CPMT: Exported to ACBF", export_success)
161
162 # Export and package CBZ and Epub.
163 if export_success:
164 if "CBZ" in sizesList.keys():
165 export_success = self.export_to_cbz(exportPath)
166 print("CPMT: Exported to CBZ", export_success)
167 if "EPUB" in sizesList.keys():
168 self.progress.setLabelText(i18n("Saving out EPUB"))
169 self.progress.setValue(self.progress.value()+1)
170 export_success = exporters.EPUB.export(self.configDictionary, self.projectURL, self.pagesLocationListpagesLocationList["EPUB"], self.acbfPageDataacbfPageData)
171 print("CPMT: Exported to EPUB", export_success)
172 else:
173 QMessageBox.warning(None, i18n("Export not Possible"), i18n("Nothing to export, URL not set."), QMessageBox.StandardButton.Ok)
174 print("CPMT: Nothing to export, url not set.")
175
176 return export_success
177
178 """
179 This calls up all the functions necessary for making a cbz.
180 """
181
182 def export_to_cbz(self, exportPath):
183 title = self.configDictionary["projectName"]
184 if "title" in self.configDictionary.keys():
185 title = str(self.configDictionary["title"]).replace(" ", "_")
186 self.progress.setLabelText(i18n("Saving out CoMet\nmetadata file"))
187 self.progress.setValue(self.progress.value()+1)
188 self.cometLocation = str(exportPath / "metadata" / str(title + " CoMet.xml"))
189 export_success = exporters.CoMet.write_xml(self.configDictionary, self.pagesLocationListpagesLocationList["CBZ"], self.cometLocation)
190 self.comicRackInfo = str(exportPath / "metadata" / "ComicInfo.xml")
191 self.progress.setLabelText(i18n("Saving out Comicrack\nmetadata file"))
192 self.progress.setValue(self.progress.value()+1)
193 export_success = exporters.comic_rack_xml.write_xml(self.configDictionary, self.pagesLocationListpagesLocationList["CBZ"], self.comicRackInfo)
194 self.package_cbz(exportPath)
195 return export_success
196
197 def save_out_pngs(self, sizesList):
198 # A small fix to ensure crop to guides is set.
199 if "cropToGuides" not in self.configDictionary.keys():
200 self.configDictionary["cropToGuides"] = False
201
202 # Check if we have pages at all...
203 if "pages" in self.configDictionary.keys():
204
205 # Check if there's export methods, and if so make sure the appropriate dictionaries are initialised.
206 if len(sizesList.keys()) < 1:
207 QMessageBox.warning(None, i18n("Export not Possible"), i18n("Export failed because there's no export settings configured."), QMessageBox.StandardButton.Ok)
208 print("CPMT: Export failed because there's no export methods set.")
209 return False
210 else:
211 for key in sizesList.keys():
213
214 # Get the appropriate paths.
215 path = Path(self.projectURL)
216 exportPath = path / self.configDictionary["exportLocation"]
217 pagesList = self.configDictionary["pages"]
218 fileName = str(exportPath)
219
220 """
221 Mini function to handle the setup of this string.
222 """
223 def timeString(timePassed, timeEstimated):
224 return str(i18n("Time passed: {passedString}\n Estimated: {estimated}")).format(passedString=timePassed, estimated=timeEstimated)
225
226 for p in range(0, len(pagesList)):
227 pagesDone = str(i18n("{pages} of {pagesTotal} done.")).format(pages=p, pagesTotal=len(pagesList))
228
229 # Update the label in the progress dialog.
230 self.progress.setValue(p)
231 timePassed = self.timer.elapsed()
232 if p > 0:
233 timeEstimated = (len(pagesList) - p) * (timePassed / p)
234 estimatedString = self.parseTime(timeEstimated)
235 else:
236 estimatedString = str(u"\u221E")
237 passedString = self.parseTime(timePassed)
238 self.progress.setLabelText("\n".join([pagesDone, timeString(passedString, estimatedString), i18n("Opening next page")]))
239 QApplication.instance().processEvents()
240 # Get the appropriate url and open the page.
241 url = str(Path(self.projectURL) / pagesList[p])
242 page = Application.openDocument(url)
243 page.waitForDone()
244
245 # Update the progress bar a little
246 self.progress.setLabelText("\n".join([pagesDone, timeString(self.parseTime(self.timer.elapsed()), estimatedString), i18n("Cleaning up page")]))
247
248 # remove layers and flatten.
249 labelList = self.configDictionary["labelsToRemove"]
250 panelsAndText = []
251
252 # These three lines are what is causing the page not to close.
253 root = page.rootNode()
254 self.getPanelsAndText(root, panelsAndText)
255 self.removeLayers(labelList, root)
256 page.refreshProjection()
257 # We'll need the offset and scale for aligning the panels and text correctly. We're getting this from the CBZ
258
259 pageData = {}
260 pageData["vector"] = panelsAndText
261 tree = ET.fromstring(page.documentInfo())
262 pageData["title"] = page.name()
263 calligra = "{http://www.calligra.org/DTD/document-info}"
264 about = tree.find(calligra + "about")
265 keywords = about.find(calligra + "keyword")
266 keys = str(keywords.text).split(",")
267 pKeys = []
268 for key in keys:
269 if key in self.pageKeys:
270 pKeys.append(key)
271 pageData["keys"] = pKeys
272 page.flatten()
273 page.waitForDone()
274 batchsave = Application.batchmode()
275 Application.setBatchmode(True)
276 # Start making the format specific copy.
277 for key in sizesList.keys():
278
279 # Update the progress bar a little
280 self.progress.setLabelText("\n".join([pagesDone, timeString(self.parseTime(self.timer.elapsed()), estimatedString), str(i18n("Exporting for {key}")).format(key=key)]))
281
282 w = sizesList[key]
283 # copy over data
284 projection = page.clone()
285 projection.setBatchmode(True)
286 # Crop. Cropping per guide only happens if said guides have been found.
287 if w["Crop"] is True:
288 listHGuides = []
289 listHGuides = page.guidesConfig().horizontalGuides()
290 listHGuides.sort()
291 for i in range(len(listHGuides) - 1, 0, -1):
292 if listHGuides[i] < 0 or listHGuides[i] > page.height():
293 listHGuides.pop(i)
294 listVGuides = page.guidesConfig().verticalGuides()
295 listVGuides.sort()
296 for i in range(len(listVGuides) - 1, 0, -1):
297 if listVGuides[i] < 0 or listVGuides[i] > page.width():
298 listVGuides.pop(i)
299 if self.configDictionary["cropToGuides"] and len(listVGuides) > 1:
300 cropx = listVGuides[0]
301 cropw = listVGuides[-1] - cropx
302 else:
303 cropx = self.configDictionary["cropLeft"]
304 cropw = page.width() - self.configDictionary["cropRight"] - cropx
305 if self.configDictionary["cropToGuides"] and len(listHGuides) > 1:
306 cropy = listHGuides[0]
307 croph = listHGuides[-1] - cropy
308 else:
309 cropy = self.configDictionary["cropTop"]
310 croph = page.height() - self.configDictionary["cropBottom"] - cropy
311 projection.crop(int(cropx), int(cropy), int(cropw), int(croph))
312 projection.waitForDone()
313 QApplication.instance().processEvents()
314 # resize appropriately
315 else:
316 cropx = 0
317 cropy = 0
318 res = page.resolution()
319 listScales = [projection.width(), projection.height(), res, res]
320 projectionOldSize = [projection.width(), projection.height()]
321 sizesCalc = sizesCalculator()
322 listScales = sizesCalc.get_scale_from_resize_config(config=w, listSizes=listScales)
323 projection.scaleImage(listScales[0], listScales[1], listScales[2], listScales[3], "bicubic")
324 projection.waitForDone()
325 QApplication.instance().processEvents()
326 # png, gif and other webformats should probably be in 8bit srgb at maximum.
327 if key != "TIFF":
328 if (projection.colorModel() != "RGBA" and projection.colorModel() != "GRAYA") or projection.colorDepth() != "U8":
329 projection.setColorSpace("RGBA", "U8", "sRGB built-in")
330 else:
331 # Tiff on the other hand can handle all the colormodels, but can only handle integer bit depths.
332 # Tiff is intended for print output, and 16 bit integer will be sufficient.
333 if projection.colorDepth() != "U8" or projection.colorDepth() != "U16":
334 projection.setColorSpace(page.colorModel(), "U16", page.colorProfile())
335 # save
336 # Make sure the folder name for this export exists. It'll allow us to keep the
337 # export folders nice and clean.
338 folderName = str(key + "-" + w["FileType"])
339 if Path(exportPath / folderName).exists() is False:
340 Path.mkdir(exportPath / folderName)
341 # Get a nice and descriptive file name.
342 fn = str(Path(exportPath / folderName) / str("page_" + format(p, "03d") + "_" + str(listScales[0]) + "x" + str(listScales[1]) + "." + w["FileType"]))
343 # Finally save and add the page to a list of pages. This will make it easy for the packaging function to
344 # find the pages and store them.
345 projection.exportImage(fn, InfoObject())
346 projection.waitForDone()
347 QApplication.instance().processEvents()
348 if key == "CBZ" or key == "EPUB":
349 transform = {}
350 transform["offsetX"] = cropx
351 transform["offsetY"] = cropy
352 transform["resDiff"] = page.resolution() / 72
353 transform["scaleWidth"] = projection.width() / projectionOldSize[0]
354 transform["scaleHeight"] = projection.height() / projectionOldSize[1]
355 pageData["transform"] = transform
356 self.pagesLocationListpagesLocationList[key].append(fn)
357 projection.close()
358 self.acbfPageDataacbfPageData.append(pageData)
359 page.close()
360 self.progress.setValue(len(pagesList))
361 Application.setBatchmode(batchsave)
362 # TODO: Check what or whether memory leaks are still caused and otherwise remove the entry below.
363 print("CPMT: Export has finished. If there are memory leaks, they are caused by file layers.")
364 return True
365 print("CPMT: Export not happening because there aren't any pages.")
366 QMessageBox.warning(None, i18n("Export not Possible"), i18n("Export not happening because there are no pages."), QMessageBox.StandardButton.Ok)
367 return False
368
369 """
370 Function to get the panel and text data.
371 """
372
373 def getPanelsAndText(self, node, list):
374 textLayersToSearch = ["text"]
375 panelLayersToSearch = ["panels"]
376 if "textLayerNames" in self.configDictionary.keys():
377 textLayersToSearch = self.configDictionary["textLayerNames"]
378 if "panelLayerNames" in self.configDictionary.keys():
379 panelLayersToSearch = self.configDictionary["panelLayerNames"]
380 if node.type() == "vectorlayer":
381 for name in panelLayersToSearch:
382 if str(name).lower() in str(node.name()).lower():
383 for shape in node.shapes():
384 if (shape.type() == "groupshape"):
385 self.getPanelsAndTextVector(shape, list)
386 else:
387 self.handleShapeDescription(shape, list)
388 for name in textLayersToSearch:
389 if str(name).lower() in str(node.name()).lower():
390 for shape in node.shapes():
391 if (shape.type() == "groupshape"):
392 self.getPanelsAndTextVector(shape, list, True)
393 else:
394 self.handleShapeDescription(shape, list, True)
395 else:
396 if node.childNodes():
397 for child in node.childNodes():
398 self.getPanelsAndText(node=child, list=list)
399
400 def parseTime(self, time = 0):
401 timeList = []
402 timeList.append(str(int(time / 60000)))
403 timeList.append(format(int((time%60000) / 1000), "02d"))
404 timeList.append(format(int(time % 1000), "03d"))
405 return ":".join(timeList)
406 """
407 Function to get the panel and text data from a group shape
408 """
409
410 def getPanelsAndTextVector(self, group, list, textOnly=False):
411 for shape in group.children():
412 if (shape.type() == "groupshape"):
413 self.getPanelsAndTextVector(shape, list, textOnly)
414 else:
415 self.handleShapeDescription(shape, list, textOnly)
416 """
417 Function to get text and panels in a format that acbf will accept
418 """
419
420 def handleShapeDescription(self, shape, list, textOnly=False):
421 if (shape.type() != "KoSvgTextShapeID" and textOnly is True):
422 return
423 shapeDesc = {}
424 shapeDesc["name"] = shape.name()
425 rect = shape.boundingBox()
426 listOfPoints = [rect.topLeft(), rect.topRight(), rect.bottomRight(), rect.bottomLeft()]
427 shapeDesc["boundingBox"] = listOfPoints
428 if (shape.type() == "KoSvgTextShapeID" and textOnly is True):
429 shapeDesc["text"] = shape.toSvg()
430 list.append(shapeDesc)
431 return
432 # Turn off shape retrieval for now until the new text tool is finished.
433 r"""
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(r'[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.FillRule.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(r"<.*?>", " ", string)
551 string = string.replace(" ", " ")
552 width = min(QFontMetrics(font).horizontalAdvance(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 """
604 Function to remove layers when they have the given labels.
605
606 If not, but the node does have children, check those too.
607 """
608
609 def removeLayers(self, labels, node):
610 deleteList = []
611 if node.colorLabel() in labels:
612 node.remove()
613 deleteList.append(node)
614 else:
615 if node.childNodes():
616 for child in node.childNodes():
617 self.removeLayers(labels, node=child)
618 for node in deleteList:
619 del node
620
621 """
622 package cbz puts all the meta-data and relevant files into an zip file ending with ".cbz"
623 """
624
625 def package_cbz(self, exportPath):
626
627 # Use the project name if there's no title to avoid sillyness with unnamed zipfiles.
628 title = self.configDictionary["projectName"]
629 if "title" in self.configDictionary.keys():
630 title = str(self.configDictionary["title"]).replace(" ", "_")
631
632 # Get the appropriate path.
633 url = str(exportPath / str(title + ".cbz"))
634
635 # Create a zip file.
636 cbzArchive = zipfile.ZipFile(url, mode="w", compression=zipfile.ZIP_STORED)
637
638 # Add all the meta data files.
639 cbzArchive.write(self.acbfLocation, Path(self.acbfLocation).name)
640 cbzArchive.write(self.cometLocation, Path(self.cometLocation).name)
641 cbzArchive.write(self.comicRackInfo, Path(self.comicRackInfo).name)
642 comic_book_info_json_dump = str()
643 self.progress.setLabelText(i18n("Saving out Comicbook\ninfo metadata file"))
644 self.progress.setValue(self.progress.value()+1)
645 comic_book_info_json_dump = exporters.comic_book_info.writeJson(self.configDictionary)
646 cbzArchive.comment = comic_book_info_json_dump.encode("utf-8")
647
648 # Add the pages.
649 if "CBZ" in self.pagesLocationListpagesLocationList.keys():
650 for page in self.pagesLocationListpagesLocationList["CBZ"]:
651 if (Path(page).exists()):
652 cbzArchive.write(page, Path(page).name)
653 self.progress.setLabelText(i18n("Packaging CBZ"))
654 self.progress.setValue(self.progress.value()+1)
655 # Close the zip file when done.
656 cbzArchive.close()
float value(const T *src, size_t ch)