diff --git a/plugins/python/comics_project_management_tools/README.html b/plugins/python/comics_project_management_tools/README.html index e2014cbfad..f2bacc0d83 100644 --- a/plugins/python/comics_project_management_tools/README.html +++ b/plugins/python/comics_project_management_tools/README.html @@ -1,241 +1,247 @@ Comics Project Management Tools Plugin Manual

Comics Project Management Tools

This is the Comics Project Management Tools python plugin for Krita.

CPMT aims to simplify comics creation by:

Export-wise, CPMT aims to support:

Advanced Comic Book Format
An open comics format that has detailed markup as well as support for translations.
CBZ
the most popular comic file format, with the following meta-data schemes:
Epub
The epub publishing format. Not the most ideal format for handling comics, but most readers can open epub.

Table of Contents:

  1. Usage - quick-start guide
  2. Usage - Meta Data
    1. Adding extra auto-completion keys.
    2. The Author list
  3. Usage - Pages
  4. Usage - Copy Location
  5. Usage - Export
    1. ACBF

Usage - quick-start guide:

First, get the comic manager docker(settings → dockers → comic Management Docker). There, select New Project.

It will show a dialog asking for:

The project directory.
This is where everything will be written to.
Concept
so a simple sentence explaining what you want to write the comic about. This concept is just for you.
Project name.
This is not the title, but more of a code name which will be used to create pages. For the impatient artist there is even a generator that produces code names.
Language
The main language, used for all the meta data. By default set to the system locale.
Make a new directory with the project name.
Whether to make a new project directory inside the selected directory. This allows you to have a generic comics directory that you always select and that CPMT will make directories named with the project name inside.
Pages
The name for the directory to store the pages. This is where new pages are placed.
Export
The name for the directory to store the export. This is where the comic will be exported to.
Templates
The name for the directory to store the template. This is where the page templates get stored.
Translations
The translations directory is where the POT file will be stored and where the exporter searches for translation(PO) files.

It will also allow you to edit meta data if you'd want already, but this is not mandatory.

Then after you finish, select Open Project, go to the location where you have stored your comics project. There should be a “comicsConfig.json” file there, next to the new folders for the pages, templates and export. Open that.

Now, click Add Page to add your first page. You will get a dialog asking for the template. Here you can generate one, or import one. CPMT will remember this as the default one.

Double click the new page to open in Krita.

The second column in the docker allows you to see the “subject” line in the document info if it's filled in.

You can press the arrow next to Add Page to get more features, like Add Existing Page, Remove Page, or Batch Resize.

Usage - Meta Data

You can edit the meta data by clicking the dropdown next to Project Settings and selecting Meta Data.

There's quite a few fields here, because there's quite a few different types of meta data. Hover over the fields to get an idea of what needs to be typed.

The meta data is intended to be filled out over the course of the project, so don't worry too much if you cannot instantly think of what a certain entry should be.

The meta data fields have auto completion wherever sensible. You can add your own meta data fields as noted in the following section:

Adding extra auto-completion keys.

First, you need to go to project settings, and there point the extra keys to a folder where extra keys can be found.

It will search that extra folder for the following folders:

You can add extra auto-completion keys by adding a text file with each new key on a separate line to one of the “key” folders. The name of the text file doesn't matter. This way you can add characters by universe, or archive specific keywords by archive name.

So for example, the following file has three superhero names on different lines, nothing more, nothing less.

 Spider-Man
  Hawkeye
  Jean Grey

When you then store it as marvel.txt put into the directory “key_characters”, Krita will use the names from the list as suggestion for the character field in the meta-data.

The exception is the key_ratings, which uses CSV files, using the top row to determine the title, and then has the rating in the first column, and the description on the second. This allows the description to show up as tool-tips.

The Author list

The author list is a table containing all the authors of the project. It allows a distinction between given, family, middle and nickname, as well as role, email and homepage.

You can rearrange the author list by drag and dropping the number at the left, as well as adding and removing authors.

Adding an author will always add “John Doe”. You can double click the names and cells to change their contents. For the role, there are auto completion keys, so to encourage using standardized ways to describe their roles.

In the main docker, there's an option under the pages actions called Scrape Authors, this will make the comics project docker search the pages in the pages list for author info and append that to the author list. It will not attempt to check for duplicates, so be sure to the list afterwards.

Usage - Project Settings

The project settings allows you to change all the technical details of the project:

Usage - Pages

There's several other things you can do with pages. You can either access these feature by clicking the drop-down next to Add Page or right-clicking the pages list.

Adding pages
You can add pages by pressing the Add Page button. The first time you press this, it'll ask for a template. After you create or select a template it will use this as the default. You can set the default in the project settings.
Adding pages from template:
Adding pages from a template will always give the template dialog. This will allow you to have several different templates in the templates directory(it will show all the kra files in the templates directory), so that you can have spread, coverpages and other pages at your finger tips. The create template dialog will allow you to make a simple two layer image with a white background, and rulers for the bleeds and guides. Import template will copy selected templates to the template directory, keeping all the necessary files inside the comics project.
Remove a page
This allows you to remove the selected page in the list from the pages list. It does NOT delete the page from the disk.
Adding existing pages
This is for when you wish to add existing pages, either because you removed the page from the list, or because you already have a project going and wish to add the pages to the list.
Batch Resize
This will show a window with resize options. After selecting the right options, all the pages will be resized as such. A progress dialog will pop up showing you which pages have been done and how long it will take based on the passed time.
View Page in Window
This will pop up a dialog with the selected page's mergedimage.png. The dialog will update when doing this for the image of another page. This is so that you can have a quick reference for a single page in the event your other referencing tools cannot open kra files.
Scrape Authors
This searches all the files from the pages list for author information and adds that to the author list. It will not check for copies, so you will need to clean up the author list yourself.
Scrape Text for Translations
This triggers a script that will go over each page and take out certain text information it can find. It will use the 'text layer keys' in the export dialog to determine whether a vector layer's text ought to be considered. Then, when done, it will put the text it found into a POT file, together with translatable meta-data, like the comic title, and will save it in the translations folder. The POT file can then be used by translators(using something like PO edit) to create a PO file. The CPMT can in turn use the PO files in the creation of ACBF files which'll embed the translations.
Rearranging pages
You can rearrange pages by moving the number on the left of the page up or down.

Usage - Copy Location

Copy location, the button underneath the export button, allows you to copy the current project location to clipboard. Just press it, and paste somewhere else. This is useful when using multiple programs and reference tools and you just want to quickly navigate to the project directory.

Usage - Export

CPMT will not allow export without any export methods set.

You can configure the export settings by going to the drop-down next to Project Settings and selecting Export Settings.

Here you can define...

Once you've done that, press export. Krita will pop up a progress bar for you with the estimated time and progress, so you can estimate how long you will have to wait.

CPMT will store the resized files and meta data in separate folders in the export folder. This is so that you can perform optimization methods afterwards and update everything quickly.

ACBF

ACBF is the advanced comic book format. It is a metadata file that can hold extra data like panels and text, and can even store translations for the text.

+

When you generate a CBZ file, the ACBF file will be generated alongside of it. There's in fact two ACBF files being generated: The one in the metadata folder is the ACBF file as it is inside the CBZ. The other ACBF file, next to the CBZ is the standalonefile. This file has the pages embedded, but there's currently fewer viewers who can read it.

ACBF has a set amount of genres it can cover. This is the default list of genre auto completion keys. Genres outside that will be put into the extra keywords list for ACBF. On top of that, it does allow defining a match to this genre. To set a match to a genre, write a number in brackets indicating how much it matches this genre. So for example "Horror(60), Science Fiction(40)" will have Horror set to 60% and Science Fiction set to 40%. These values are normalized. So if you put in "Romance(550), Fantasy(650)" it will ensure that the two values will become percentages, leading to Romance being set to 46% and Fantasy set to 54%.

The CPMT has some support for frames and text export. If you name a vector layer “text” or “panels” it will search those for shapes. The shapes that are text nodes will be added to the ACBF file as a text in the main language of the comic, using the bounding box of the text-shape. The shapes that aren't text will have their bounding boxes used as frames. The order of frames and text is determined by the shape z-order in Krita, with the bottom shape being the first and the top shape being the last. You can customize these layer selection keys in the export settings dialog.

The CPMT also support translations. ACBF will use the PO files stored in the translations folder. In the export dialog, you can configure whether you want translation comments to be embeded. Then, if there's translation comments in the PO file, ACBF will put those in the reference section and add a link to the line with the translation comment. Translations will have translator note headers. You can configure these in the export, and they will also be put into the POT file when it is generated so they may be translated.

-

When you generate a CBZ file, the ACBF file will be generated alongside of it. There's in fact two ACBF files being generated: The one in the metadata folder is the ACBF file as it is inside the CBZ. The other ACBF file, next to the CBZ is the standalonefile. This file has the pages embedded, but there's currently fewer viewers who can read it.

+

Finally, there's the styles and the text type. You can configure the styles in the export settings dialog tab for acbf. The exporter will use the configuration and alignment to automatically figure out the text-type in the export.

+

To fine tune the export to ACBF, you can go to file→document Information and add the following keywords:

acbf_title
this will flag this page to be used as a table of contents bookmark inside ACBF. The content mark will use the “title” value in the document information to create a bookmark in the project language.
acbf_none
Sets the page transition value to “none” explicitly.
acbf_fade
Sets the page transitio to fade. Viewers that support it will fade to black into this page.
acbf_blend
Sets the page transition to blend. Viewers that support it will fade the previous page to this one.
acbf_horizontal
Sets the page transition to scroll_right. Viewers that support it will scroll right to a new page.
acbf_vertical
Sets the page transition to scroll_down. Viewers that support it will scroll down to a new page.
diff --git a/plugins/python/comics_project_management_tools/README.md b/plugins/python/comics_project_management_tools/README.md index a3d4a8d25a..d40af92efb 100644 --- a/plugins/python/comics_project_management_tools/README.md +++ b/plugins/python/comics_project_management_tools/README.md @@ -1,247 +1,230 @@ Comics Project Management Tools =============================== This is the Comics Project Management Tools python plugin for Krita. CPMT aims to simplify comics creation by: * Giving the artist a way to organize and quickly access their pages. * Helping the artist(s) deal with the boring bits meta data bits of a comic project by giving a meta-data editor that gives suggestions, explanation and occasionally a dab of humor. * Making export set-and-forget type of affair where a single click can export to multiple formats with proper meta-data. Export-wise, CPMT aims to support: Advanced Comic Book Format : An open comics format that has detailed markup as well as support for translations. CBZ : the most popular comic file format, with the following meta-data schemes: * ACBF - as above. * CoMet.xml * ComicBookInfo (Spec is unclear so not 100% certain) * ComicInfo.xml(Comic Rack) Epub : The epub publishing format. Not the most ideal format for handling comics, but most readers can open epub. Usage - quick-start guide: ------------------------- First, get the comic manager docker(settings → dockers → comic Management Docker). There, select *New Project*. It will show a dialog asking for: The project directory. : This is where everything will be written to. Concept : so a simple sentence explaining what you want to write the comic about. This concept is just for you. Project name. : This is not the title, but more of a code name which will be used to create pages. For the impatient artist there is even a generator that produces code names. Language : The main language, used for all the meta data. By default set to the system locale. Make a new directory with the project name. : Whether to make a new project directory inside the selected directory. This allows you to have a generic comics directory that you always select and that CPMT will make directories named with the project name inside. Pages : The name for the directory to store the pages. This is where new pages are placed. Export : The name for the directory to store the export. This is where the comic will be exported to. Templates : The name for the directory to store the template. This is where the page templates get stored. It will also allow you to edit meta data if you'd want already, but this is not mandatory. Then after you finish, select *Open Project*, go to the location where you have stored your comics project. There should be a "comicsConfig.json" file there, next to the new folders for the pages, templates and export. Open that. Now, click *Add Page* to add your first page. You will get a dialog asking for the template. Here you can generate one, or import one. CPMT will remember this as the default one. Double click the new page to open in Krita. The second column in the docker allows you to see the "subject" line in the document info if it's filled in. You can press the arrow next to *Add Page* to get more features, like *Add Existing Page*, *Remove Page*, or *Batch Resize*. Usage - Meta Data ------------------ You can edit the meta data by clicking the dropdown next to *Project Settings* and selecting *Meta Data*. There's quite a few fields here, because there's quite a few different types of meta data. Hover over the fields to get an idea of what needs to be typed. The meta data is intended to be filled out over the course of the project, so don't worry too much if you cannot instantly think of what a certain entry should be. The meta data fields have auto completion wherever sensible. You can add your own meta data fields as noted in the following section: ### Adding extra auto-completion keys. First, you need to go to project settings, and there point the extra keys to a folder where extra keys can be found. It will search that extra folder for the following folders: * key_genre * key_format * key_rating * key_characters * key_author_roles * key_other You can add extra auto-completion keys by adding a text file with each new key on a separate line to one of the "key" folders. The name of the text file doesn't matter. This way you can add characters by universe, or archive specific keywords by archive name. So for example, the following file has three superhero names on different lines, nothing more, nothing less. ``` Spider-Man Hawkeye Jean Grey ``` When you then store it as marvel.txt put into the directory "key_characters", Krita will use the names from the list as suggestion for the character field in the meta-data. The exception is the key_ratings, which uses CSV files, using the top row to determine the title, and then has the rating in the first column, and the description on the second. This allows the description to show up as tool-tips. ### The Author list The author list is a table containing all the authors of the project. It allows a distinction between given, family, middle and nickname, as well as role, email and homepage. You can rearrange the author list by drag and dropping the number at the left, as well as adding and removing authors. Adding an author will always add "John Doe". You can double click the names and cells to change their contents. For the role, there are auto completion keys, so to encourage using standardized ways to describe their roles. In the main docker, there's an option under the pages actions called *Scrape Authors*, this will make the comics project docker search the pages in the pages list for author info and append that to the author list. It will not attempt to check for duplicates, so be sure to the list afterwards. Usage - Project Settings ----------------------- The project settings allows you to change all the technical details of the project: * the project name * the concept * the location of pages, export and templates * the default template. * the location of the extra auto-completion keys(see metadata) Usage - Pages ------------- There's several other things you can do with pages. You can either access these feature by clicking the drop-down next to *Add Page* or right-clicking the pages list. Adding pages : You can add pages by pressing the *Add Page* button. The first time you press this, it'll ask for a template. After you create or select a template it will use this as the default. You can set the default in the project settings. Adding pages from template: : Adding pages from a template will always give the template dialog. This will allow you to have several different templates in the templates directory(it will show all the kra files in the templates directory), so that you can have spread, coverpages and other pages at your finger tips. The create template dialog will allow you to make a simple two layer image with a white background, and rulers for the bleeds and guides. Import template will copy selected templates to the template directory, keeping all the necessary files inside the comics project. Remove a page : This allows you to remove the selected page in the list from the pages list. It does NOT delete the page from the disk. Adding existing pages : This is for when you wish to add existing pages, either because you removed the page from the list, or because you already have a project going and wish to add the pages to the list. Batch Resize : This will show a window with resize options. After selecting the right options, all the pages will be resized as such. A progress dialog will pop up showing you which pages have been done and how long it will take based on the passed time. View Page in Window : This will pop up a dialog with the selected page's mergedimage.png. The dialog will update when doing this for the image of another page. This is so that you can have a quick reference for a single page in the event your other referencing tools cannot open kra files. Scrape Authors : This searches all the files from the pages list for author information and adds that to the author list. It will not check for copies, so you will need to clean up the author list yourself. Rearranging pages : You can rearrange pages by moving the number on the left of the page up or down. Usage - Copy Location -------------------- Copy location, the button underneath the export button, allows you to copy the current project location to clipboard. Just press it, and paste somewhere else. This is useful when using multiple programs and reference tools and you just want to quickly navigate to the project directory. Usage - Export -------------- CPMT will not allow export without any export methods set. You can configure the export settings by going to the drop-down next to *Project Settings* and selecting *Export Settings*. Here you can define... * how much a page needs to be cropped * which layers to remove by layer color-label * to which formats to export, in what file-format and how to resize. Once you've done that, press export. Krita will pop up a progress bar for you with the estimated time and progress, so you can estimate how long you will have to wait. CPMT will store the resized files and meta data in separate folders in the export folder. This is so that you can perform optimization methods afterwards and update everything quickly. ### ACBF ### ACBF is the advanced comic book format. It is a metadata file that can hold extra data like panels and text, and can even store translations for the text. Krita has some support for frames and text. If you name a vector layer "text" or "panels" it will search those for shapes. The shapes that are text nodes will be added to the ACBF file as a text in the main language of the comic, using the bounding box of the text-shape. The shapes that aren't text will have their bounding boxes used as frames. The order of frames and text is determined by the shape z-order in Krita, with the bottom shape being the first and the top shape being the last. When you generate a CBZ file, the ACBF file will be generated alongside of it. There's in fact two ACBF files being generated: The one in the metadata folder is the ACBF file as it is inside the CBZ. The other ACBF file, next to the CBZ is the standalonefile. This file has the pages embedded, but there's currently fewer viewers who can read it. To fine tune the export to ACBF, you can go to file->document Information and add the following keywords: acbf_title : this will flag this page to be used as a table of contents bookmark inside ACBF. The content mark will use the "title" value in the document information to create a bookmark in the project language. acbf_none : Sets the page transition value to "none" explicitly. acbf_fade : Sets the page transitio to fade. Viewers that support it will fade to black into this page. acbf_blend : Sets the page transition to blend. Viewers that support it will fade the previous page to this one. acbf_horizontal : Sets the page transition to scroll_right. Viewers that support it will scroll right to a new page. acbf_vertical : Sets the page transition to scroll_down. Viewers that support it will scroll down to a new page. TODO: ====== Things I still want to do: * Krita: - Generate text from the author list. (Requires text api) * clean up path relativeness. (Not sure how much better this can be done) * Make label removal just a list? (unsure) * PNG and JPEG export settings. * maybe use python minidom for acbf(or export in general), because then we can create a prettier xml file, which is necessary for helping people edit the files in question. [partialy done, epub still needs this] ACBF list: * support getting text info from the vector layers. [Partially done] - Don't forget text-rotation(Needs API) - - maybe text-class can be used to determine type? (We'll proly need to use style sheets and comparing the text format to those) - + Speech (speech, dialogue) - + Commentary (caption in american comics) - + Formal (For justified aligned text, like big chunks of text.) - + Letter (Like a letter in a comic) - + Code (Monospaced font) - + Heading (Chapter title) - + Audio (Only meant for audio devices) - + Thought (Thought bubbles and the like) - + Sign (For signs on buildings and the like.) - + Inverted (Whether or not this should be treated as inverted text) - + transparent(For a transparent wordballoon.) - + Question: Where is general sound effects? Like, if we make a distinction between speech and thought, why are general sound effects missing? (Admittedly, I'd prefer if we could allow putting sound effects and such as a base64 reffed bit.) (See 1.2 support) - + Probably also support acbf_order[n], where n is used to be the definitive value of the order. - + Text class battle plan: + - Probably also support acbf_order[n], where n is used to be the definitive value of the order. + - Text class battle plan: 1. Support title and desc in vector shapes(c++) 2. Support editing them from a right-click "properties" window. 3. Support getting those values from python. 4. have acbf_textclass read from the desc. - Get page background color from the page if possible. (This might only work with canvas color, though) - Jump: proly not gonna support this. - - text url anchors: don't have a sensible interface for this. (Might use it for references only) + - text url anchors: only used for references. - Font embedding: spec is super vague about this. - - Style sheets: needs a very complex gui. - database reference: I am not sure if this is at all relevant for Krita? Because it implies an already published comic. - - acbf doc info should use author with first/nick/last/etc seperate as well[partially done] - text-layer background color(maybe sample topleft of projection???) -* 1.2 support: - - text type: Sound.[needs gui still] diff --git a/plugins/python/comics_project_management_tools/comics_export_dialog.py b/plugins/python/comics_project_management_tools/comics_export_dialog.py index 980015a4b9..570ad9d605 100644 --- a/plugins/python/comics_project_management_tools/comics_export_dialog.py +++ b/plugins/python/comics_project_management_tools/comics_export_dialog.py @@ -1,624 +1,624 @@ """ Copyright (c) 2017 Wolthera van Hövell tot Westerflier This file is part of the Comics Project Management Tools(CPMT). CPMT is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. CPMT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with the CPMT. If not, see . """ """ A dialog for editing the exporter settings. """ from PyQt5.QtGui import QStandardItem, QStandardItemModel, QColor, QFont from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QGroupBox, QFormLayout, QCheckBox, QComboBox, QSpinBox, QWidget, QVBoxLayout, QHBoxLayout, QTabWidget, QPushButton, QLineEdit, QLabel, QListView, QTableView, QFontComboBox, QSpacerItem from PyQt5.QtCore import Qt, QUuid from krita import * """ A generic widget to make selecting size easier. It works by initialising with a config name(like "scale"), and then optionally setting the config with a dictionary. Then, afterwards, you get the config with a dictionary, with the config name being the entry the values are under. """ class comic_export_resize_widget(QGroupBox): configName = "" def __init__(self, configName, batch=False, fileType=True): super().__init__() self.configName = configName self.setTitle("Adjust Workingfile") formLayout = QFormLayout() self.setLayout(formLayout) self.crop = QCheckBox(i18n("Crop files before resize.")) self.cmbFile = QComboBox() self.cmbFile.addItems(["png", "jpg", "webp"]) self.resizeMethod = QComboBox() self.resizeMethod.addItems([i18n("Percentage"), i18n("DPI"), i18n("Maximum Width"), i18n("Maximum Height")]) self.resizeMethod.currentIndexChanged.connect(self.slot_set_enabled) self.spn_DPI = QSpinBox() self.spn_DPI.setMaximum(1200) self.spn_DPI.setSuffix(i18n(" DPI")) self.spn_DPI.setValue(72) self.spn_PER = QSpinBox() if batch is True: self.spn_PER.setMaximum(1000) else: self.spn_PER.setMaximum(100) self.spn_PER.setSuffix(" %") self.spn_PER.setValue(100) self.spn_width = QSpinBox() self.spn_width.setMaximum(99999) self.spn_width.setSuffix(" px") self.spn_width.setValue(800) self.spn_height = QSpinBox() self.spn_height.setMaximum(99999) self.spn_height.setSuffix(" px") self.spn_height.setValue(800) if batch is False: formLayout.addRow("", self.crop) if fileType is True and configName != "TIFF": formLayout.addRow(i18n("File Type"), self.cmbFile) formLayout.addRow(i18n("Method:"), self.resizeMethod) formLayout.addRow(i18n("DPI:"), self.spn_DPI) formLayout.addRow(i18n("Percentage:"), self.spn_PER) formLayout.addRow(i18n("Width:"), self.spn_width) formLayout.addRow(i18n("Height:"), self.spn_height) self.slot_set_enabled() def slot_set_enabled(self): method = self.resizeMethod.currentIndex() self.spn_DPI.setEnabled(False) self.spn_PER.setEnabled(False) self.spn_width.setEnabled(False) self.spn_height.setEnabled(False) if method is 0: self.spn_PER.setEnabled(True) if method is 1: self.spn_DPI.setEnabled(True) if method is 2: self.spn_width.setEnabled(True) if method is 3: self.spn_height.setEnabled(True) def set_config(self, config): if self.configName in config.keys(): mConfig = config[self.configName] if "Method" in mConfig.keys(): self.resizeMethod.setCurrentIndex(mConfig["Method"]) if "FileType" in mConfig.keys(): self.cmbFile.setCurrentText(mConfig["FileType"]) if "Crop" in mConfig.keys(): self.crop.setChecked(mConfig["Crop"]) if "DPI" in mConfig.keys(): self.spn_DPI.setValue(mConfig["DPI"]) if "Percentage" in mConfig.keys(): self.spn_PER.setValue(mConfig["Percentage"]) if "Width" in mConfig.keys(): self.spn_width.setValue(mConfig["Width"]) if "Height" in mConfig.keys(): self.spn_height.setValue(mConfig["Height"]) self.slot_set_enabled() def get_config(self, config): mConfig = {} mConfig["Method"] = self.resizeMethod.currentIndex() if self.configName == "TIFF": mConfig["FileType"] = "tiff" else: mConfig["FileType"] = self.cmbFile.currentText() mConfig["Crop"] = self.crop.isChecked() mConfig["DPI"] = self.spn_DPI.value() mConfig["Percentage"] = self.spn_PER.value() mConfig["Width"] = self.spn_width.value() mConfig["Height"] = self.spn_height.value() config[self.configName] = mConfig return config """ Quick combobox for selecting the color label. """ class labelSelector(QComboBox): def __init__(self): super(labelSelector, self).__init__() lisOfColors = [] lisOfColors.append(Qt.transparent) lisOfColors.append(QColor(91, 173, 220)) lisOfColors.append(QColor(151, 202, 63)) lisOfColors.append(QColor(247, 229, 61)) lisOfColors.append(QColor(255, 170, 63)) lisOfColors.append(QColor(177, 102, 63)) lisOfColors.append(QColor(238, 50, 51)) lisOfColors.append(QColor(191, 106, 209)) lisOfColors.append(QColor(118, 119, 114)) self.itemModel = QStandardItemModel() for color in lisOfColors: item = QStandardItem() item.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled) item.setCheckState(Qt.Unchecked) item.setText(" ") item.setData(color, Qt.BackgroundColorRole) self.itemModel.appendRow(item) self.setModel(self.itemModel) def getLabels(self): listOfIndexes = [] for i in range(self.itemModel.rowCount()): index = self.itemModel.index(i, 0) item = self.itemModel.itemFromIndex(index) if item.checkState(): listOfIndexes.append(i) return listOfIndexes def setLabels(self, listOfIndexes): for i in listOfIndexes: index = self.itemModel.index(i, 0) item = self.itemModel.itemFromIndex(index) item.setCheckState(True) """ The comic export settings dialog will allow configuring the export. This config consists of... * Crop settings. for removing bleeds. * Selecting layer labels to remove. * Choosing which formats to export to. * Choosing how to resize these * Whether to crop. * Which file type to use. And for ACBF, it gives the ability to edit acbf document info. """ class comic_export_setting_dialog(QDialog): - acbfStylesList = ["default text", "speech", "commentary", "formal", "letter", "code", "heading", "audio", "thought", "sign", "sound", "emphasis", "strong"] + acbfStylesList = ["speech", "commentary", "formal", "letter", "code", "heading", "audio", "thought", "sign", "sound", "emphasis", "strong"] def __init__(self): super().__init__() self.setLayout(QVBoxLayout()) self.setWindowTitle(i18n("Export settings")) buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) buttons.accepted.connect(self.accept) buttons.rejected.connect(self.reject) mainWidget = QTabWidget() self.layout().addWidget(mainWidget) self.layout().addWidget(buttons) # Set basic crop settings # Set which layers to remove before export. mainExportSettings = QWidget() mainExportSettings.setLayout(QVBoxLayout()) groupExportCrop = QGroupBox(i18n("Crop settings")) formCrop = QFormLayout() groupExportCrop.setLayout(formCrop) self.chk_toOutmostGuides = QCheckBox(i18n("Crop to outmost guides")) self.chk_toOutmostGuides.setChecked(True) self.chk_toOutmostGuides.setToolTip(i18n("This will crop to the outmost guides if possible and otherwise use the underlying crop settings.")) formCrop.addRow("", self.chk_toOutmostGuides) btn_fromSelection = QPushButton(i18n("Set margins from active selection")) btn_fromSelection.clicked.connect(self.slot_set_margin_from_selection) # This doesn't work. formCrop.addRow("", btn_fromSelection) self.spn_marginLeft = QSpinBox() self.spn_marginLeft.setMaximum(99999) self.spn_marginLeft.setSuffix(" px") formCrop.addRow(i18n("Left:"), self.spn_marginLeft) self.spn_marginTop = QSpinBox() self.spn_marginTop.setMaximum(99999) self.spn_marginTop.setSuffix(" px") formCrop.addRow(i18n("Top:"), self.spn_marginTop) self.spn_marginRight = QSpinBox() self.spn_marginRight.setMaximum(99999) self.spn_marginRight.setSuffix(" px") formCrop.addRow(i18n("Right:"), self.spn_marginRight) self.spn_marginBottom = QSpinBox() self.spn_marginBottom.setMaximum(99999) self.spn_marginBottom.setSuffix(" px") formCrop.addRow(i18n("Bottom:"), self.spn_marginBottom) groupExportLayers = QGroupBox(i18n("Layers")) formLayers = QFormLayout() groupExportLayers.setLayout(formLayers) self.cmbLabelsRemove = labelSelector() formLayers.addRow(i18n("Label for removal:"), self.cmbLabelsRemove) self.ln_text_layer_name = QLineEdit() self.ln_text_layer_name.setToolTip(i18n("These are keywords that can be used to identify text layers. A layer only needs to contain the keyword to be recognised. Keywords should be comma seperated.")) self.ln_panel_layer_name = QLineEdit() self.ln_panel_layer_name.setToolTip(i18n("These are keywords that can be used to identify panel layers. A layer only needs to contain the keyword to be recognised. Keywords should be comma seperated.")) formLayers.addRow(i18n("Text Layer Key:"), self.ln_text_layer_name) formLayers.addRow(i18n("Panel Layer Key:"), self.ln_panel_layer_name) mainExportSettings.layout().addWidget(groupExportCrop) mainExportSettings.layout().addWidget(groupExportLayers) mainWidget.addTab(mainExportSettings, i18n("General")) # CBZ, crop, resize, which metadata to add. CBZexportSettings = QWidget() CBZexportSettings.setLayout(QVBoxLayout()) self.CBZactive = QCheckBox(i18n("Export to CBZ")) CBZexportSettings.layout().addWidget(self.CBZactive) self.CBZgroupResize = comic_export_resize_widget("CBZ") CBZexportSettings.layout().addWidget(self.CBZgroupResize) self.CBZactive.clicked.connect(self.CBZgroupResize.setEnabled) CBZgroupMeta = QGroupBox(i18n("Metadata to add")) # CBZexportSettings.layout().addWidget(CBZgroupMeta) CBZgroupMeta.setLayout(QFormLayout()) mainWidget.addTab(CBZexportSettings, "CBZ") # ACBF, crop, resize, creator name, version history, panel layer, text layers. ACBFExportSettings = QWidget() ACBFform = QFormLayout() ACBFExportSettings.setLayout(QVBoxLayout()) ACBFdocInfo = QGroupBox() ACBFdocInfo.setTitle(i18n("ACBF Document Info")) ACBFdocInfo.setLayout(ACBFform) self.lnACBFSource = QLineEdit() self.lnACBFSource.setToolTip(i18n("Whether the acbf file is an adaption of an existing source, and if so, how to find information about that source. So for example, for an adapted webcomic, the official website url should go here.")) self.lnACBFID = QLabel() self.lnACBFID.setToolTip(i18n("By default this will be filled with a generated universal unique identifier. The ID by itself is merely so that comic book library management programs can figure out if this particular comic is already in their database and whether it has been rated. Of course, the UUID can be changed into something else by manually changing the json, but this is advanced usage.")) self.spnACBFVersion = QSpinBox() self.ACBFhistoryModel = QStandardItemModel() acbfHistoryList = QListView() acbfHistoryList.setModel(self.ACBFhistoryModel) btn_add_history = QPushButton(i18n("Add history entry")) btn_add_history.clicked.connect(self.slot_add_history_item) self.chkIncludeTranslatorComments = QCheckBox() self.chkIncludeTranslatorComments.setText(i18n("Include Translator's Comments")) self.chkIncludeTranslatorComments.setToolTip(i18n("A PO file can contain translator's comments. If this is checked, the translations comments will be added as references into the ACBF file.")) self.lnTranslatorHeader = QLineEdit() ACBFform.addRow(i18n("Source:"), self.lnACBFSource) ACBFform.addRow(i18n("ACBF UID:"), self.lnACBFID) ACBFform.addRow(i18n("Version:"), self.spnACBFVersion) ACBFform.addRow(i18n("Version History:"), acbfHistoryList) ACBFform.addRow("", btn_add_history) ACBFform.addRow("", self.chkIncludeTranslatorComments) ACBFform.addRow(i18n("Translator Header:"), self.lnTranslatorHeader) ACBFAuthorInfo = QWidget() acbfAVbox = QVBoxLayout(ACBFAuthorInfo) infoLabel = QLabel(i18n("The people responsible for the generation of the CBZ/ACBF files.")) infoLabel.setWordWrap(True) ACBFAuthorInfo.layout().addWidget(infoLabel) self.ACBFauthorModel = QStandardItemModel(0, 6) labels = [i18n("Nick Name"), i18n("Given Name"), i18n("Middle Name"), i18n("Family Name"), i18n("Email"), i18n("Homepage")] self.ACBFauthorModel.setHorizontalHeaderLabels(labels) self.ACBFauthorTable = QTableView() acbfAVbox.addWidget(self.ACBFauthorTable) self.ACBFauthorTable.setModel(self.ACBFauthorModel) self.ACBFauthorTable.verticalHeader().setDragEnabled(True) self.ACBFauthorTable.verticalHeader().setDropIndicatorShown(True) self.ACBFauthorTable.verticalHeader().setSectionsMovable(True) self.ACBFauthorTable.verticalHeader().sectionMoved.connect(self.slot_reset_author_row_visual) AuthorButtons = QHBoxLayout() btn_add_author = QPushButton(i18n("Add author")) btn_add_author.clicked.connect(self.slot_add_author) AuthorButtons.addWidget(btn_add_author) btn_remove_author = QPushButton(i18n("Remove author")) btn_remove_author.clicked.connect(self.slot_remove_author) AuthorButtons.addWidget(btn_remove_author) acbfAVbox.addLayout(AuthorButtons) ACBFStyle = QWidget() ACBFStyle.setLayout(QHBoxLayout()) self.ACBFStylesModel = QStandardItemModel() self.ACBFStyleClass = QListView() self.ACBFStyleClass.setModel(self.ACBFStylesModel) ACBFStyle.layout().addWidget(self.ACBFStyleClass) ACBFStyleEdit = QWidget() ACBFStyleEditVB = QVBoxLayout(ACBFStyleEdit) self.ACBFfontCombo = QFontComboBox() self.ACBFBold = QCheckBox(i18n("Bold")) self.ACBFItal = QCheckBox(i18n("Italic")) self.ACBFStyleClass.clicked.connect(self.slot_set_style) self.ACBFStyleClass.selectionModel().selectionChanged.connect(self.slot_set_style) self.ACBFStylesModel.itemChanged.connect(self.slot_set_style) self.ACBFfontCombo.currentFontChanged.connect(self.slot_font_current_style) self.ACBFfontCombo.setEditable(False) self.ACBFBold.toggled.connect(self.slot_font_current_style) self.ACBFItal.toggled.connect(self.slot_font_current_style) ACBFStyleEditVB.addWidget(self.ACBFfontCombo) ACBFStyleEditVB.addWidget(self.ACBFBold) ACBFStyleEditVB.addWidget(self.ACBFItal) ACBFStyleEditVB.addStretch() ACBFStyle.layout().addWidget(ACBFStyleEdit) ACBFTabwidget = QTabWidget() ACBFTabwidget.addTab(ACBFdocInfo, i18n("Document Info")) ACBFTabwidget.addTab(ACBFAuthorInfo, i18n("Author Info")) ACBFTabwidget.addTab(ACBFStyle, i18n("Style sheet")) ACBFExportSettings.layout().addWidget(ACBFTabwidget) mainWidget.addTab(ACBFExportSettings, i18n("ACBF")) # Epub export, crop, resize, other questions. EPUBexportSettings = QWidget() EPUBexportSettings.setLayout(QVBoxLayout()) self.EPUBactive = QCheckBox(i18n("Export to EPUB")) EPUBexportSettings.layout().addWidget(self.EPUBactive) self.EPUBgroupResize = comic_export_resize_widget("EPUB") EPUBexportSettings.layout().addWidget(self.EPUBgroupResize) self.EPUBactive.clicked.connect(self.EPUBgroupResize.setEnabled) mainWidget.addTab(EPUBexportSettings, "EPUB") # For Print. Crop, no resize. TIFFExportSettings = QWidget() TIFFExportSettings.setLayout(QVBoxLayout()) self.TIFFactive = QCheckBox(i18n("Export to TIFF")) TIFFExportSettings.layout().addWidget(self.TIFFactive) self.TIFFgroupResize = comic_export_resize_widget("TIFF") TIFFExportSettings.layout().addWidget(self.TIFFgroupResize) self.TIFFactive.clicked.connect(self.TIFFgroupResize.setEnabled) mainWidget.addTab(TIFFExportSettings, "TIFF") # SVG, crop, resize, embed vs link. #SVGExportSettings = QWidget() #mainWidget.addTab(SVGExportSettings, "SVG") """ Add a history item to the acbf version history list. """ def slot_add_history_item(self): newItem = QStandardItem() newItem.setText("v" + str(self.spnACBFVersion.value()) + "-" + i18n("in this version...")) self.ACBFhistoryModel.appendRow(newItem) """ Get the margins by treating the active selection in a document as the trim area. This allows people to snap selections to a vector or something, and then get the margins. """ def slot_set_margin_from_selection(self): doc = Application.activeDocument() if doc is not None: if doc.selection() is not None: self.spn_marginLeft.setValue(doc.selection().x()) self.spn_marginTop.setValue(doc.selection().y()) self.spn_marginRight.setValue(doc.width() - (doc.selection().x() + doc.selection().width())) self.spn_marginBottom.setValue(doc.height() - (doc.selection().y() + doc.selection().height())) """ Add an author with default values initialised. """ def slot_add_author(self): listItems = [] listItems.append(QStandardItem(i18n("Anon"))) # Nick name listItems.append(QStandardItem(i18n("John"))) # First name listItems.append(QStandardItem()) # Middle name listItems.append(QStandardItem(i18n("Doe"))) # Last name listItems.append(QStandardItem()) # email listItems.append(QStandardItem()) # homepage self.ACBFauthorModel.appendRow(listItems) """ Remove the selected author from the author list. """ def slot_remove_author(self): self.ACBFauthorModel.removeRow(self.ACBFauthorTable.currentIndex().row()) """ Ensure that the drag and drop of authors doesn't mess up the labels. """ def slot_reset_author_row_visual(self): headerLabelList = [] for i in range(self.ACBFauthorTable.verticalHeader().count()): headerLabelList.append(str(i)) for i in range(self.ACBFauthorTable.verticalHeader().count()): logicalI = self.ACBFauthorTable.verticalHeader().logicalIndex(i) headerLabelList[logicalI] = str(i + 1) self.ACBFauthorModel.setVerticalHeaderLabels(headerLabelList) def slot_set_style(self): index = self.ACBFStyleClass.currentIndex() if index.isValid(): item = self.ACBFStylesModel.item(index.row()) font = QFont() font.setFamily(str(item.data(role=Qt.UserRole+1))) self.ACBFfontCombo.setCurrentFont(font) bold = item.data(role=Qt.UserRole+2) if bold is not None: self.ACBFBold.setChecked(bold) italic = item.data(role=Qt.UserRole+3) if italic is not None: self.ACBFItal.setChecked(italic) def slot_font_current_style(self): index = self.ACBFStyleClass.currentIndex() if index.isValid(): item = self.ACBFStylesModel.item(index.row()) font = QFont(self.ACBFfontCombo.currentFont()) item.setData(font.family(), role=Qt.UserRole+1) item.setData(self.ACBFBold.isChecked(), role=Qt.UserRole+2) item.setData(self.ACBFItal.isChecked(), role=Qt.UserRole+3) self.ACBFStylesModel.setItem(index.row(), item) """ Load the UI values from the config dictionary given. """ def setConfig(self, config): if "cropToGuides" in config.keys(): self.chk_toOutmostGuides.setChecked(config["cropToGuides"]) if "cropLeft" in config.keys(): self.spn_marginLeft.setValue(config["cropLeft"]) if "cropTop" in config.keys(): self.spn_marginTop.setValue(config["cropTop"]) if "cropRight" in config.keys(): self.spn_marginRight.setValue(config["cropRight"]) if "cropBottom" in config.keys(): self.spn_marginBottom.setValue(config["cropBottom"]) if "labelsToRemove" in config.keys(): self.cmbLabelsRemove.setLabels(config["labelsToRemove"]) if "textLayerNames" in config.keys(): self.ln_text_layer_name.setText(", ".join(config["textLayerNames"])) else: self.ln_text_layer_name.setText("text") if "panelLayerNames" in config.keys(): self.ln_panel_layer_name.setText(", ".join(config["panelLayerNames"])) else: self.ln_panel_layer_name.setText("panels") self.CBZgroupResize.set_config(config) if "CBZactive" in config.keys(): self.CBZactive.setChecked(config["CBZactive"]) self.EPUBgroupResize.set_config(config) if "EPUBactive" in config.keys(): self.EPUBactive.setChecked(config["EPUBactive"]) self.TIFFgroupResize.set_config(config) if "TIFFactive" in config.keys(): self.TIFFactive.setChecked(config["TIFFactive"]) if "acbfAuthor" in config.keys(): if isinstance(config["acbfAuthor"], list): for author in config["acbfAuthor"]: listItems = [] listItems.append(QStandardItem(author.get("nickname", ""))) listItems.append(QStandardItem(author.get("first-name", ""))) listItems.append(QStandardItem(author.get("initials", ""))) listItems.append(QStandardItem(author.get("last-name", ""))) listItems.append(QStandardItem(author.get("email", ""))) listItems.append(QStandardItem(author.get("homepage", ""))) self.ACBFauthorModel.appendRow(listItems) pass else: listItems = [] listItems.append(QStandardItem(config["acbfAuthor"])) # Nick name for i in range(0, 5): listItems.append(QStandardItem()) # First name self.ACBFauthorModel.appendRow(listItems) if "acbfSource" in config.keys(): self.lnACBFSource.setText(config["acbfSource"]) if "acbfID" in config.keys(): self.lnACBFID.setText(config["acbfID"]) else: self.lnACBFID.setText(QUuid.createUuid().toString()) if "acbfVersion" in config.keys(): self.spnACBFVersion.setValue(config["acbfVersion"]) if "acbfHistory" in config.keys(): for h in config["acbfHistory"]: item = QStandardItem() item.setText(h) self.ACBFhistoryModel.appendRow(item) if "acbfStyles" in config.keys(): styleDict = config.get("acbfStyles", {}) for key in self.acbfStylesList: keyDict = styleDict.get(key, {}) style = QStandardItem(key.title()) style.setCheckable(True) if key in styleDict.keys(): style.setCheckState(Qt.Checked) else: style.setCheckState(Qt.Unchecked) style.setData(keyDict.get("font", QFont().family()), role=Qt.UserRole+1) style.setData(keyDict.get("bold", False), role=Qt.UserRole+2) style.setData(keyDict.get("ital", False), role=Qt.UserRole+3) self.ACBFStylesModel.appendRow(style) else: for key in self.acbfStylesList: style = QStandardItem(key.title()) style.setCheckable(True) style.setCheckState(Qt.Unchecked) style.setData(QFont().family(), role=Qt.UserRole+1) style.setData(False, role=Qt.UserRole+2) #Bold style.setData(False, role=Qt.UserRole+3) #Italic self.ACBFStylesModel.appendRow(style) self.CBZgroupResize.setEnabled(self.CBZactive.isChecked()) self.lnTranslatorHeader.setText(config.get("translatorHeader", "Translator's Notes")) self.chkIncludeTranslatorComments.setChecked(config.get("includeTranslComment", False)) """ Store the GUI values into the config dictionary given. @return the config diactionary filled with new values. """ def getConfig(self, config): config["cropToGuides"] = self.chk_toOutmostGuides.isChecked() config["cropLeft"] = self.spn_marginLeft.value() config["cropTop"] = self.spn_marginTop.value() config["cropBottom"] = self.spn_marginRight.value() config["cropRight"] = self.spn_marginBottom.value() config["labelsToRemove"] = self.cmbLabelsRemove.getLabels() config["CBZactive"] = self.CBZactive.isChecked() config = self.CBZgroupResize.get_config(config) config["EPUBactive"] = self.EPUBactive.isChecked() config = self.EPUBgroupResize.get_config(config) config["TIFFactive"] = self.TIFFactive.isChecked() config = self.TIFFgroupResize.get_config(config) authorList = [] for row in range(self.ACBFauthorTable.verticalHeader().count()): logicalIndex = self.ACBFauthorTable.verticalHeader().logicalIndex(row) listEntries = ["nickname", "first-name", "initials", "last-name", "email", "homepage"] author = {} for i in range(len(listEntries)): entry = self.ACBFauthorModel.data(self.ACBFauthorModel.index(logicalIndex, i)) if entry is None: entry = " " if entry.isspace() is False and len(entry) > 0: author[listEntries[i]] = entry elif listEntries[i] in author.keys(): author.pop(listEntries[i]) authorList.append(author) config["acbfAuthor"] = authorList config["acbfSource"] = self.lnACBFSource.text() config["acbfID"] = self.lnACBFID.text() config["acbfVersion"] = self.spnACBFVersion.value() versionList = [] for r in range(self.ACBFhistoryModel.rowCount()): index = self.ACBFhistoryModel.index(r, 0) versionList.append(self.ACBFhistoryModel.data(index, Qt.DisplayRole)) config["acbfHistory"] = versionList acbfStylesDict = {} for row in range(0, self.ACBFStylesModel.rowCount()): entry = self.ACBFStylesModel.item(row) if entry.checkState() == Qt.Checked: key = entry.text().lower() style = {} font = entry.data(role=Qt.UserRole+1) if font is not None: style["font"] = font bold = entry.data(role=Qt.UserRole+2) if bold is not None: style["bold"] = bold italic = entry.data(role=Qt.UserRole+3) if italic is not None: style["ital"] = italic acbfStylesDict[key] = style config["acbfStyles"] = acbfStylesDict config["translatorHeader"] = self.lnTranslatorHeader.text() config["includeTranslComment"] = self.chkIncludeTranslatorComments.isChecked() # Turn this into something that retreives from a line-edit when string freeze is over. config["textLayerNames"] = self.ln_text_layer_name.text().split(",") config["panelLayerNames"] = self.ln_panel_layer_name.text().split(",") return config diff --git a/plugins/python/comics_project_management_tools/exporters/CPMT_ACBF_XML_Exporter.py b/plugins/python/comics_project_management_tools/exporters/CPMT_ACBF_XML_Exporter.py index 33dd6257b0..d7aa7d392b 100644 --- a/plugins/python/comics_project_management_tools/exporters/CPMT_ACBF_XML_Exporter.py +++ b/plugins/python/comics_project_management_tools/exporters/CPMT_ACBF_XML_Exporter.py @@ -1,622 +1,648 @@ """ Copyright (c) 2018 Wolthera van Hövell tot Westerflier This file is part of the Comics Project Management Tools(CPMT). CPMT is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. CPMT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with the CPMT. If not, see . """ """ Write the Advanced Comic Book Data xml file. http://acbf.wikia.com/wiki/ACBF_Specifications """ import os import re from PyQt5.QtCore import QDate, Qt, QPointF, QByteArray, QBuffer from PyQt5.QtGui import QImage from PyQt5.QtXml import QDomDocument, QDomElement, QDomText, QDomNodeList from . import CPMT_po_parser as po_parser def write_xml(configDictionary = {}, pageData = [], pagesLocationList = [], locationBasic = str(), locationStandAlone = str(), projectUrl = str()): acbfGenreList = ["science_fiction", "fantasy", "adventure", "horror", "mystery", "crime", "military", "real_life", "superhero", "humor", "western", "manga", "politics", "caricature", "sports", "history", "biography", "education", "computer", "religion", "romance", "children", "non-fiction", "adult", "alternative", "other", "artbook"] acbfAuthorRolesList = ["Writer", "Adapter", "Artist", "Penciller", "Inker", "Colorist", "Letterer", "Cover Artist", "Photographer", "Editor", "Assistant Editor", "Translator", "Other", "Designer"] document = QDomDocument() root = document.createElement("ACBF") root.setAttribute("xmlns", "http://www.fictionbook-lib.org/xml/acbf/1.0") document.appendChild(root) if "acbfStyles" in configDictionary.keys(): stylesDictionary = configDictionary.get("acbfStyles", {}) styleString = "\n" for key in stylesDictionary: style = stylesDictionary.get(key, {}) if key == "emphasis" or key == "strong": styleClass = key+"{\n" - elif key == "default": + elif key == "speech": styleClass = "text-area {\n" else: styleClass = "text-area[type=\""+key+"\"] {\n" styleString += styleClass if "font" in style.keys(): styleString += " font-family:"+style["font"]+";\n" if "bold" in style.keys(): if style["bold"]: - styleString += " font-weight: 700;\n" - else: - styleString += " font-weight: 400;\n" + styleString += " font-weight: bold;\n" if "italic" in style.keys(): if style["italic"]: styleString += " font-style: italic;\n" else: styleString += " font-style: normal;\n" styleString += "}\n" style = document.createElement("style") style.setAttribute("type", "text/css") style.appendChild(document.createTextNode(styleString)) root.appendChild(style) meta = document.createElement("meta-data") translationFolder = configDictionary.get("translationLocation", "translations") fullTranslationPath = os.path.join(projectUrl, translationFolder) poParser = po_parser.po_file_parser(fullTranslationPath, True) bookInfo = document.createElement("book-info") if "authorList" in configDictionary.keys(): for authorE in range(len(configDictionary["authorList"])): author = document.createElement("author") authorDict = configDictionary["authorList"][authorE] if "first-name" in authorDict.keys(): authorN = document.createElement("first-name") authorN.appendChild(document.createTextNode(str(authorDict["first-name"]))) author.appendChild(authorN) if "last-name" in authorDict.keys(): authorN = document.createElement("last-name") authorN.appendChild(document.createTextNode(str(authorDict["last-name"]))) author.appendChild(authorN) if "initials" in authorDict.keys(): authorN = document.createElement("middle-name") authorN.appendChild(document.createTextNode(str(authorDict["initials"]))) author.appendChild(authorN) if "nickname" in authorDict.keys(): authorN = document.createElement("nickname") authorN.appendChild(document.createTextNode(str(authorDict["nickname"]))) author.appendChild(authorN) if "homepage" in authorDict.keys(): authorN = document.createElement("home-page") authorN.appendChild(document.createTextNode(str(authorDict["homepage"]))) author.appendChild(authorN) if "email" in authorDict.keys(): authorN = document.createElement("email") authorN.appendChild(document.createTextNode(str(authorDict["email"]))) author.appendChild(authorN) if "role" in authorDict.keys(): if str(authorDict["role"]).title() in acbfAuthorRolesList: author.setAttribute("activity", str(authorDict["role"])) if "language" in authorDict.keys(): author.setAttribute("lang", str(authorDict["language"])) bookInfo.appendChild(author) bookTitle = document.createElement("book-title") if "title" in configDictionary.keys(): bookTitle.appendChild(document.createTextNode(str(configDictionary["title"]))) else: bookTitle.appendChild(document.createTextNode(str("Comic with no Name"))) bookInfo.appendChild(bookTitle) extraGenres = [] if "genre" in configDictionary.keys(): genreListConf = configDictionary["genre"] if isinstance(configDictionary["genre"], dict): genreListConf = configDictionary["genre"].keys() for genre in genreListConf: genreModified = str(genre).lower() genreModified.replace(" ", "_") if genreModified in acbfGenreList: bookGenre = document.createElement("genre") bookGenre.appendChild(document.createTextNode(str(genreModified))) if isinstance(configDictionary["genre"], dict): genreMatch = configDictionary["genre"][genreModified] if genreMatch>0: bookGenre.setAttribute("match", str(genreMatch)) bookInfo.appendChild(bookGenre) else: extraGenres.append(genre) annotation = document.createElement("annotation") if "summary" in configDictionary.keys(): paragraphList = str(configDictionary["summary"]).split("\n") for para in paragraphList: p = document.createElement("p") p.appendChild(document.createTextNode(str(para))) annotation.appendChild(p) else: p = document.createElement("p") p.appendChild(document.createTextNode(str("There was no summary upon generation of this file."))) annotation.appendChild(p) bookInfo.appendChild(annotation) if "characters" in configDictionary.keys(): character = document.createElement("characters") for name in configDictionary["characters"]: char = document.createElement("name") char.appendChild(document.createTextNode(str(name))) character.appendChild(char) bookInfo.appendChild(character) keywords = document.createElement("keywords") stringKeywordsList = [] for key in extraGenres: stringKeywordsList.append(str(key)) if "otherKeywords" in configDictionary.keys(): for key in configDictionary["otherKeywords"]: stringKeywordsList.append(str(key)) if "format" in configDictionary.keys(): for key in configDictionary["format"]: stringKeywordsList.append(str(key)) keywords.appendChild(document.createTextNode(", ".join(stringKeywordsList))) bookInfo.appendChild(keywords) coverpageurl = "" coverpage = document.createElement("coverpage") if "pages" in configDictionary.keys(): if "cover" in configDictionary.keys(): pageList = [] pageList = configDictionary["pages"] coverNumber = max([pageList.index(configDictionary["cover"]), 0]) image = document.createElement("image") if len(pagesLocationList) >= coverNumber: coverpageurl = pagesLocationList[coverNumber] image.setAttribute("href", os.path.basename(coverpageurl)) coverpage.appendChild(image) bookInfo.appendChild(coverpage) if "language" in configDictionary.keys(): language = document.createElement("languages") textlayer = document.createElement("text-layer") textlayer.setAttribute("lang", configDictionary["language"]) textlayer.setAttribute("show", "False") textlayerNative = document.createElement("text-layer") textlayerNative.setAttribute("lang", configDictionary["language"]) textlayerNative.setAttribute("show", "True") language.appendChild(textlayer) language.appendChild(textlayerNative) translationComments = {} for lang in poParser.get_translation_list(): textlayer = document.createElement("text-layer") textlayer.setAttribute("lang", lang) textlayer.setAttribute("show", "True") language.appendChild(textlayer) translationComments[lang] = [] translation = poParser.get_entry_for_key("@meta-title", lang).get("trans", None) if translation is not None: bookTitleTr = document.createElement("book-title") bookTitleTr.setAttribute("lang", lang) bookTitleTr.appendChild(document.createTextNode(translation)) bookInfo.appendChild(bookTitleTr) translation = poParser.get_entry_for_key("@meta-summary", lang).get("trans", None) if translation is not None: annotationTr = document.createElement("annotation") annotationTr.setAttribute("lang", lang) annotationTr.appendChild(document.createTextNode(translation)) bookInfo.appendChild(annotationTr) translation = poParser.get_entry_for_key("@meta-keywords", lang).get("trans", None) if translation is not None: keywordsTr = document.createElement("keywords") keywordsTr.setAttribute("lang", lang) keywordsTr.appendChild(document.createTextNode(translation)) bookInfo.appendChild(keywordsTr) bookInfo.appendChild(language) bookTitle.setAttribute("lang", configDictionary["language"]) annotation.setAttribute("lang", configDictionary["language"]) keywords.setAttribute("lang", configDictionary["language"]) #database = document.createElement("databaseref") # bookInfo.appendChild(database) if "seriesName" in configDictionary.keys(): sequence = document.createElement("sequence") sequence.setAttribute("title", configDictionary["seriesName"]) if "seriesVolume" in configDictionary.keys(): sequence.setAttribute("volume", str(configDictionary["seriesVolume"])) if "seriesNumber" in configDictionary.keys(): sequence.appendChild(document.createTextNode(str(configDictionary["seriesNumber"]))) else: sequence.appendChild(document.createTextNode(str(0))) bookInfo.appendChild(sequence) contentrating = document.createElement("content-rating") if "rating" in configDictionary.keys(): contentrating.appendChild(document.createTextNode(str(configDictionary["rating"]))) else: contentrating.appendChild(document.createTextNode(str("Unrated."))) if "ratingSystem" in configDictionary.keys(): contentrating.setAttribute("type", configDictionary["ratingSystem"]) bookInfo.appendChild(contentrating) if "readingDirection" in configDictionary.keys(): readingDirection = document.createElement("reading-direction") if configDictionary["readingDirection"] is "rightToLeft": readingDirection.appendChild(document.createTextNode(str("RTL"))) else: readingDirection.appendChild(document.createTextNode(str("LTR"))) bookInfo.appendChild(readingDirection) meta.appendChild(bookInfo) publisherInfo = document.createElement("publish-info") if "publisherName" in configDictionary.keys(): publisherName = document.createElement("publisher") publisherName.appendChild(document.createTextNode(str(configDictionary["publisherName"]))) publisherInfo.appendChild(publisherName) if "publishingDate" in configDictionary.keys(): publishingDate = document.createElement("publish-date") publishingDate.setAttribute("value", configDictionary["publishingDate"]) publishingDate.appendChild(document.createTextNode(QDate.fromString(configDictionary["publishingDate"], Qt.ISODate).toString(Qt.SystemLocaleLongDate))) publisherInfo.appendChild(publishingDate) if "publisherCity" in configDictionary.keys(): publishCity = document.createElement("city") publishCity.appendChild(document.createTextNode(str(configDictionary["publisherCity"]))) publisherInfo.appendChild(publishCity) if "isbn-number" in configDictionary.keys(): publishISBN = document.createElement("isbn") publishISBN.appendChild(document.createTextNode(str(configDictionary["isbn-number"]))) publisherInfo.appendChild(publishISBN) license = str(configDictionary.get("license", "")) if license.isspace() is False and len(license) > 0: publishLicense = document.createElement("license") publishLicense.appendChild(document.createTextNode(license)) publisherInfo.appendChild(publishLicense) meta.appendChild(publisherInfo) documentInfo = document.createElement("document-info") # TODO: ACBF apparantly uses first/middle/last/nick/email/homepage for the document auhtor too... # The following code compensates for me not understanding this initially. This still needs # adjustments in the gui. if "acbfAuthor" in configDictionary.keys(): if isinstance(configDictionary["acbfAuthor"], list): for e in configDictionary["acbfAuthor"]: acbfAuthor = document.createElement("author") authorDict = e if "first-name" in authorDict.keys(): authorN = document.createElement("first-name") authorN.appendChild(document.createTextNode(str(authorDict["first-name"]))) acbfAuthor.appendChild(authorN) if "last-name" in authorDict.keys(): authorN = document.createElement("last-name") authorN.appendChild(document.createTextNode(str(authorDict["last-name"]))) acbfAuthor.appendChild(authorN) if "initials" in authorDict.keys(): authorN = document.createElement("middle-name") authorN.appendChild(document.createTextNode(str(authorDict["initials"]))) acbfAuthor.appendChild(authorN) if "nickname" in authorDict.keys(): authorN = document.createElement("nickname") authorN.appendChild(document.createTextNode(str(authorDict["nickname"]))) acbfAuthor.appendChild(authorN) if "homepage" in authorDict.keys(): authorN = document.createElement("homepage") authorN.appendChild(document.createTextNode(str(authorDict["home-page"]))) acbfAuthor.appendChild(authorN) if "email" in authorDict.keys(): authorN = document.createElement("email") authorN.appendChild(document.createTextNode(str(authorDict["email"]))) acbfAuthor.appendChild(authorN) if "language" in authorDict.keys(): acbfAuthor.setAttribute("lang", str(authorDict["language"])) documentInfo.appendChild(acbfAuthor) else: acbfAuthor = document.createElement("author") acbfAuthorNick = document.createElement("nickname") acbfAuthorNick.appendChild(document.createTextNode(str(configDictionary["acbfAuthor"]))) acbfAuthor.appendChild(acbfAuthorNick) documentInfo.appendChild(acbfAuthor) else: acbfAuthor = document.createElement("author") acbfAuthorNick = document.createElement("nickname") acbfAuthorNick.appendChild(document.createTextNode(str("Anon"))) acbfAuthor.appendChild(acbfAuthorNick) documentInfo.appendChild(acbfAuthor) acbfDate = document.createElement("creation-date") now = QDate.currentDate() acbfDate.setAttribute("value", now.toString(Qt.ISODate)) acbfDate.appendChild(document.createTextNode(str(now.toString(Qt.SystemLocaleLongDate)))) documentInfo.appendChild(acbfDate) if "acbfSource" in configDictionary.keys(): acbfSource = document.createElement("source") acbfSourceP = document.createElement("p") acbfSourceP.appendChild(document.createTextNode(str(configDictionary["acbfSource"]))) acbfSource.appendChild(acbfSourceP) documentInfo.appendChild(acbfSource) if "acbfID" in configDictionary.keys(): acbfID = document.createElement("id") acbfID.appendChild(document.createTextNode(str(configDictionary["acbfID"]))) documentInfo.appendChild(acbfID) if "acbfVersion" in configDictionary.keys(): acbfVersion = document.createElement("version") acbfVersion.appendChild(document.createTextNode(str(configDictionary["acbfVersion"]))) documentInfo.appendChild(acbfVersion) if "acbfHistory" in configDictionary.keys(): acbfHistory = document.createElement("history") for h in configDictionary["acbfHistory"]: p = document.createElement("p") p.appendChild(document.createTextNode(str(h))) acbfHistory.appendChild(p) documentInfo.appendChild(acbfHistory) meta.appendChild(documentInfo) root.appendChild(meta) body = document.createElement("body") references = document.createElement("references") + + def figure_out_type(svg = QDomElement()): + type = None + skipList = ["speech", "emphasis", "strong", "inverted"] + if svg.attribute("text-anchor") == "middle" or svg.attribute("text-align") == "center": + if "acbfStyles" in configDictionary.keys(): + stylesDictionary = configDictionary.get("acbfStyles", {}) + for key in stylesDictionary.keys(): + if key not in skipList: + style = stylesDictionary.get(key, {}) + font = style.get("font", "") + if svg.attribute("family") == font: + type = key + else: + type = None + elif svg.attribute("text-align") == "justified": + type = "formal" + else: + type = "commentary" + return type for p in range(0, len(pagesLocationList)): page = pagesLocationList[p] language = "en" if "language" in configDictionary.keys(): language = configDictionary["language"] textLayer = document.createElement("text-layer") textLayer.setAttribute("lang", language) data = pageData[p] transform = data["transform"] frameList = [] for v in data["vector"]: boundingBoxText = [] for point in v["boundingBox"]: offset = QPointF(transform["offsetX"], transform["offsetY"]) pixelPoint = QPointF(point.x() * transform["resDiff"], point.y() * transform["resDiff"]) newPoint = pixelPoint - offset x = int(newPoint.x() * transform["scaleWidth"]) y = int(newPoint.y() * transform["scaleHeight"]) pointText = str(x) + "," + str(y) boundingBoxText.append(pointText) if "text" in v.keys(): textArea = document.createElement("text-area") textArea.setAttribute("points", " ".join(boundingBoxText)) # TODO: Rotate will require proper global transform api as transform info is not written intotext. #textArea.setAttribute("text-rotation", str(v["rotate"])) svg = QDomDocument() svg.setContent(v["text"]) + type = figure_out_type(svg.documentElement()) paragraph = QDomDocument() paragraph.appendChild(paragraph.createElement("p")) parseTextChildren(paragraph, svg.documentElement(), paragraph.documentElement()) textArea.appendChild(paragraph.documentElement()) + if type is not None: + textArea.setAttribute("type", type) textLayer.appendChild(textArea) else: f = {} f["points"] = " ".join(boundingBoxText) frameList.append(f) textLayerList = document.createElement("trlist") for lang in poParser.get_translation_list(): textLayerTr = document.createElement("text-layer") textLayerTr.setAttribute("lang", lang) for v in data["vector"]: boundingBoxText = [] for point in v["boundingBox"]: offset = QPointF(transform["offsetX"], transform["offsetY"]) pixelPoint = QPointF(point.x() * transform["resDiff"], point.y() * transform["resDiff"]) newPoint = pixelPoint - offset x = int(newPoint.x() * transform["scaleWidth"]) y = int(newPoint.y() * transform["scaleHeight"]) pointText = str(x) + "," + str(y) boundingBoxText.append(pointText) if "text" in v.keys(): textArea = document.createElement("text-area") textArea.setAttribute("points", " ".join(boundingBoxText)) # TODO: Rotate will require proper global transform api as transform info is not written intotext. #textArea.setAttribute("text-rotation", str(v["rotate"])) + svg = QDomDocument() + svg.setContent(v["text"]) + type = figure_out_type(svg.documentElement()) string = re.sub("\<\/*?text.*?\>",'', str(v["text"])) string = re.sub("\s+?", " ", string) translationEntry = poParser.get_entry_for_key(string, lang) string = translationEntry.get("trans", string) svg.setContent(""+string+"") paragraph = QDomDocument() paragraph.appendChild(paragraph.createElement("p")) parseTextChildren(paragraph, svg.documentElement(), paragraph.documentElement()) if "translComment" in translationEntry.keys(): key = translationEntry["translComment"] listOfComments = [] listOfComments = translationComments[lang] index = 0 if key in listOfComments: index = listOfComments.index(key)+1 else: listOfComments.append(key) index = len(listOfComments) translationComments[lang] = listOfComments refID = "-".join(["tn", lang, str(index)]) anchor = document.createElement("a") anchor.setAttribute("href", "#"+refID) anchor.appendChild(document.createTextNode("*")) paragraph.documentElement().appendChild(anchor) textArea.appendChild(paragraph.documentElement()) textLayerTr.appendChild(textArea) + if type is not None: + textArea.setAttribute("type", type) textLayerList.appendChild(textLayerTr) if page is not coverpageurl: pg = document.createElement("page") image = document.createElement("image") image.setAttribute("href", os.path.basename(page)) pg.appendChild(image) if "acbf_title" in data["keys"]: title = document.createElement("title") title.setAttribute("lang", language) title.appendChild(document.createTextNode(str(data["title"]))) pg.appendChild(title) if "acbf_none" in data["keys"]: pg.setAttribute("transition", "none") if "acbf_blend" in data["keys"]: pg.setAttribute("transition", "blend") if "acbf_fade" in data["keys"]: pg.setAttribute("transition", "fade") if "acbf_horizontal" in data["keys"]: pg.setAttribute("transition", "scroll_right") if "acbf_vertical" in data["keys"]: pg.setAttribute("transition", "scroll_down") for f in frameList: frame = document.createElement("frame") frame.setAttribute("points", f["points"]) pg.appendChild(frame) pg.appendChild(textLayer) for n in range(0, textLayerList.childNodes().size()): node = textLayerList.childNodes().at(n) pg.appendChild(node) body.appendChild(pg) else: for f in frameList: frame = document.createElement("frame") frame.setAttribute("points", f["points"]) coverpage.appendChild(frame) coverpage.appendChild(textLayer) for n in range(0, textLayerList.childNodes().size()): node = textLayerList.childNodes().at(n) coverpage.appendChild(node) if configDictionary.get("includeTranslComment", False): for lang in translationComments.keys(): for key in translationComments[lang]: index = translationComments[lang].index(key)+1 refID = "-".join(["tn", lang, str(index)]) ref = document.createElement("reference") ref.setAttribute("lang", lang) ref.setAttribute("id", refID) transHeaderStr = configDictionary.get("translatorHeader", "Translator's Notes") transHeaderStr = poParser.get_entry_for_key("@meta-translator", lang).get("trans", transHeaderStr) translatorHeader = document.createElement("p") translatorHeader.appendChild(document.createTextNode(transHeaderStr+":")) ref.appendChild(translatorHeader) refPara = document.createElement("p") refPara.appendChild(document.createTextNode(key)) ref.appendChild(refPara) references.appendChild(ref) root.appendChild(body) if references.childNodes().size(): root.appendChild(references) f = open(locationBasic, 'w', newline="", encoding="utf-8") f.write(document.toString(indent=2)) f.close() success = True success = createStandAloneACBF(configDictionary, document, locationStandAlone, pagesLocationList) return success def createStandAloneACBF(configDictionary, document = QDomDocument(), location = str(), pagesLocationList = []): title = configDictionary["projectName"] if "title" in configDictionary.keys(): title = configDictionary["title"] root = document.firstChildElement("ACBF") meta = root.firstChildElement("meta-data") bookInfo = meta.firstChildElement("book-info") cover = bookInfo.firstChildElement("coverpage") body = root.firstChildElement("body") pages = [] for p in range(0, len(body.elementsByTagName("page"))): pages.append(body.elementsByTagName("page").item(p).toElement()) if (cover): pages.append(cover) data = document.createElement("data") # Covert pages to base64 strings. for i in range(0, len(pages)): image = pages[i].firstChildElement("image") href = image.attribute("href") for p in pagesLocationList: if href in p: binary = document.createElement("binary") binary.setAttribute("id", href) imageFile = QImage() imageFile.load(p) imageData = QByteArray() buffer = QBuffer(imageData) imageFile.save(buffer, "PNG") # For now always embed as png. contentType = "image/png" binary.setAttribute("content-type", contentType) binary.appendChild(document.createTextNode(str(bytearray(imageData.toBase64()).decode("ascii")))) image.setAttribute("href", "#" + href) data.appendChild(binary) root.appendChild(data) f = open(location, 'w', newline="", encoding="utf-8") f.write(document.toString(indent=2)) f.close() return True """ Function to parse svg text to acbf ready text """ def parseTextChildren(document = QDomDocument(), elRead = QDomElement(), elWrite = QDomElement()): for n in range(0, elRead.childNodes().size()): childNode = elRead.childNodes().item(n) if childNode.isText(): if elWrite.hasChildNodes() and str(childNode.nodeValue()).startswith(" ") is False: elWrite.appendChild(document.createTextNode(" ")) elWrite.appendChild(document.createTextNode(str(childNode.nodeValue()))) elif childNode.hasChildNodes(): childNode = childNode.toElement() fontWeight = str(childNode.attribute("font-weight")) fontItalic = str(childNode.attribute("font-style")) fontStrikeThrough = str(childNode.attribute("text-decoration")) fontBaseLine = str(childNode.attribute("baseline-shift")) newElementMade = False if fontItalic.isalnum(): if (fontItalic == "italic"): newElement = document.createElement("emphasis") newElementMade = True elif fontWeight.isalnum(): if (fontWeight == "bold" or int(fontWeight) > 400): newElement = document.createElement("strong") newElementMade = True elif fontStrikeThrough.isalnum(): if (fontStrikeThrough == "line-through"): newElement = document.createElement("strikethrough") newElementMade = True elif fontBaseLine.isalnum(): if (fontBaseLine == "super"): newElement = document.createElement("sup") newElementMade = True elif (fontBaseLine == "sub"): newElement = document.createElement("sub") newElementMade = True if newElementMade is True: parseTextChildren(document, childNode, newElement) elWrite.appendChild(newElement) else: parseTextChildren(document, childNode, elWrite) # If it is not a text node, nor does it have children(which could be textnodes), # we should assume it's empty and ignore it. elWrite.normalize() for e in range(0, elWrite.childNodes().size()): el = elWrite.childNodes().item(e) if el.isText(): eb = el.nodeValue() el.setNodeValue(eb.replace(" ", " "))