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.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.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 return
422 # Turn off shape retrieval for now until the new text tool is finished.
423 r"""
424 if (shape.type() != "KoSvgTextShapeID" and textOnly is True):
425 return
426 shapeDesc = {}
427 shapeDesc["name"] = shape.name()
428 rect = shape.boundingBox()
429 listOfPoints = [rect.topLeft(), rect.topRight(), rect.bottomRight(), rect.bottomLeft()]
430 shapeDoc = minidom.parseString(shape.toSvg())
431 docElem = shapeDoc.documentElement
432 svgRegExp = re.compile(r'[MLCSQHVATmlzcqshva]\d+\.?\d* \d+\.?\d*')
433 transform = docElem.getAttribute("transform")
434 coord = []
435 adjust = QTransform()
436 # TODO: If we get global transform api, use that instead of parsing manually.
437 if "translate" in transform:
438 transform = transform.replace('translate(', '')
439 for c in transform[:-1].split(" "):
440 if "," in c:
441 c = c.replace(",", "")
442 coord.append(float(c))
443 if len(coord) < 2:
444 coord.append(coord[0])
445 adjust = QTransform(1, 0, 0, 1, coord[0], coord[1])
446 if "matrix" in transform:
447 transform = transform.replace('matrix(', '')
448 for c in transform[:-1].split(" "):
449 if "," in c:
450 c = c.replace(",", "")
451 coord.append(float(c))
452 adjust = QTransform(coord[0], coord[1], coord[2], coord[3], coord[4], coord[5])
453 path = QPainterPath()
454 if docElem.localName == "path":
455 dVal = docElem.getAttribute("d")
456 listOfSvgStrings = [" "]
457 listOfSvgStrings = svgRegExp.findall(dVal)
458 if listOfSvgStrings:
459 listOfPoints = []
460 for l in listOfSvgStrings:
461 line = l[1:]
462 coordinates = line.split(" ")
463 if len(coordinates) < 2:
464 coordinates.append(coordinates[0])
465 x = float(coordinates[-2])
466 y = float(coordinates[-1])
467 offset = QPointF()
468 if l.islower():
469 offset = listOfPoints[0]
470 if l.lower().startswith("m"):
471 path.moveTo(QPointF(x, y) + offset)
472 elif l.lower().startswith("h"):
473 y = listOfPoints[-1].y()
474 path.lineTo(QPointF(x, y) + offset)
475 elif l.lower().startswith("v"):
476 x = listOfPoints[-1].x()
477 path.lineTo(QPointF(x, y) + offset)
478 elif l.lower().startswith("c"):
479 path.cubicTo(coordinates[0], coordinates[1], coordinates[2], coordinates[3], x, y)
480 else:
481 path.lineTo(QPointF(x, y) + offset)
482 path.setFillRule(Qt.FillRule.WindingFill)
483 for polygon in path.simplified().toSubpathPolygons(adjust):
484 for point in polygon:
485 listOfPoints.append(point)
486 elif docElem.localName == "rect":
487 listOfPoints = []
488 if (docElem.hasAttribute("x")):
489 x = float(docElem.getAttribute("x"))
490 else:
491 x = 0
492 if (docElem.hasAttribute("y")):
493 y = float(docElem.getAttribute("y"))
494 else:
495 y = 0
496 w = float(docElem.getAttribute("width"))
497 h = float(docElem.getAttribute("height"))
498 path.addRect(QRectF(x, y, w, h))
499 for point in path.toFillPolygon(adjust):
500 listOfPoints.append(point)
501 elif docElem.localName == "ellipse":
502 listOfPoints = []
503 if (docElem.hasAttribute("cx")):
504 x = float(docElem.getAttribute("cx"))
505 else:
506 x = 0
507 if (docElem.hasAttribute("cy")):
508 y = float(docElem.getAttribute("cy"))
509 else:
510 y = 0
511 ry = float(docElem.getAttribute("ry"))
512 rx = float(docElem.getAttribute("rx"))
513 path.addEllipse(QPointF(x, y), rx, ry)
514 for point in path.toFillPolygon(adjust):
515 listOfPoints.append(point)
516 elif docElem.localName == "text":
517 # NOTE: This only works for horizontal preformated text. Vertical text needs a different
518 # ordering of the rects, and wraparound should try to take the shape it is wrapped in.
519 family = "sans-serif"
520 if docElem.hasAttribute("font-family"):
521 family = docElem.getAttribute("font-family")
522 size = "11"
523 if docElem.hasAttribute("font-size"):
524 size = docElem.getAttribute("font-size")
525 multilineText = True
526 for el in docElem.childNodes:
527 if el.nodeType == minidom.Node.TEXT_NODE:
528 multilineText = False
529 if multilineText:
530 listOfPoints = []
531 listOfRects = []
532
533 # First we collect all the possible line-rects.
534 for el in docElem.childNodes:
535 if docElem.hasAttribute("font-family"):
536 family = docElem.getAttribute("font-family")
537 if docElem.hasAttribute("font-size"):
538 size = docElem.getAttribute("font-size")
539 fontsize = int(size)
540 font = QFont(family, fontsize)
541 string = el.toxml()
542 string = re.sub(r"<.*?>", " ", string)
543 string = string.replace(" ", " ")
544 width = min(QFontMetrics(font).horizontalAdvance(string.strip()), rect.width())
545 height = QFontMetrics(font).height()
546 anchor = "start"
547 if docElem.hasAttribute("text-anchor"):
548 anchor = docElem.getAttribute("text-anchor")
549 top = rect.top()
550 if len(listOfRects)>0:
551 top = listOfRects[-1].bottom()
552 if anchor == "start":
553 spanRect = QRectF(rect.left(), top, width, height)
554 listOfRects.append(spanRect)
555 elif anchor == "end":
556 spanRect = QRectF(rect.right()-width, top, width, height)
557 listOfRects.append(spanRect)
558 else:
559 # Middle
560 spanRect = QRectF(rect.center().x()-(width*0.5), top, width, height)
561 listOfRects.append(spanRect)
562 # Now we have all the rects, we can check each and draw a
563 # polygon around them.
564 heightAdjust = (rect.height()-(listOfRects[-1].bottom()-rect.top()))/len(listOfRects)
565 for i in range(len(listOfRects)):
566 span = listOfRects[i]
567 addtionalHeight = i*heightAdjust
568 if i == 0:
569 listOfPoints.append(span.topLeft())
570 listOfPoints.append(span.topRight())
571 else:
572 if listOfRects[i-1].width()< span.width():
573 listOfPoints.append(QPointF(span.right(), span.top()+addtionalHeight))
574 listOfPoints.insert(0, QPointF(span.left(), span.top()+addtionalHeight))
575 else:
576 bottom = listOfRects[i-1].bottom()+addtionalHeight-heightAdjust
577 listOfPoints.append(QPointF(listOfRects[i-1].right(), bottom))
578 listOfPoints.insert(0, QPointF(listOfRects[i-1].left(), bottom))
579 listOfPoints.append(QPointF(span.right(), rect.bottom()))
580 listOfPoints.insert(0, QPointF(span.left(), rect.bottom()))
581 path = QPainterPath()
582 path.moveTo(listOfPoints[0])
583 for p in range(1, len(listOfPoints)):
584 path.lineTo(listOfPoints[p])
585 path.closeSubpath()
586 listOfPoints = []
587 for point in path.toFillPolygon(adjust):
588 listOfPoints.append(point)
589 shapeDesc["boundingBox"] = listOfPoints
590 if (shape.type() == "KoSvgTextShapeID" and textOnly is True):
591 shapeDesc["text"] = shape.toSvg()
592 list.append(shapeDesc)
593 """
594
595 """
596 Function to remove layers when they have the given labels.
597
598 If not, but the node does have children, check those too.
599 """
600
601 def removeLayers(self, labels, node):
602 if node.colorLabel() in labels:
603 node.remove()
604 else:
605 if node.childNodes():
606 for child in node.childNodes():
607 self.removeLayers(labels, node=child)
608
609 """
610 package cbz puts all the meta-data and relevant files into an zip file ending with ".cbz"
611 """
612
613 def package_cbz(self, exportPath):
614
615 # Use the project name if there's no title to avoid sillyness with unnamed zipfiles.
616 title = self.configDictionary["projectName"]
617 if "title" in self.configDictionary.keys():
618 title = str(self.configDictionary["title"]).replace(" ", "_")
619
620 # Get the appropriate path.
621 url = str(exportPath / str(title + ".cbz"))
622
623 # Create a zip file.
624 cbzArchive = zipfile.ZipFile(url, mode="w", compression=zipfile.ZIP_STORED)
625
626 # Add all the meta data files.
627 cbzArchive.write(self.acbfLocation, Path(self.acbfLocation).name)
628 cbzArchive.write(self.cometLocation, Path(self.cometLocation).name)
629 cbzArchive.write(self.comicRackInfo, Path(self.comicRackInfo).name)
630 comic_book_info_json_dump = str()
631 self.progress.setLabelText(i18n("Saving out Comicbook\ninfo metadata file"))
632 self.progress.setValue(self.progress.value()+1)
633 comic_book_info_json_dump = exporters.comic_book_info.writeJson(self.configDictionary)
634 cbzArchive.comment = comic_book_info_json_dump.encode("utf-8")
635
636 # Add the pages.
637 if "CBZ" in self.pagesLocationListpagesLocationList.keys():
638 for page in self.pagesLocationListpagesLocationList["CBZ"]:
639 if (Path(page).exists()):
640 cbzArchive.write(page, Path(page).name)
641 self.progress.setLabelText(i18n("Packaging CBZ"))
642 self.progress.setValue(self.progress.value()+1)
643 # Close the zip file when done.
644 cbzArchive.close()
float value(const T *src, size_t ch)