[PYTHON] Générer une image verticale d'un roman à partir de données textuelles

(Ajout 2019.12.14)

Pillow peut être écrit verticalement en japonais au moins en Ver6 ou version ultérieure (+ raqm 0.7 ou version ultérieure). Veuillez noter que le contenu de cet article est obsolète.

Faire

yosuruni.png

J'y ai pensé.

Constitution

Les principales bibliothèques utilisées sont les suivantes.

Décrivez la résolution de l'image générée et la disposition des colonnes en xml. Il n'y a pas de format particulier sur lequel il est basé, et c'est un format original qui a été décidé de manière appropriée. Utilisez la bibliothèque xml pour l'analyser. Le texte est écrit en texte brut de base, et les points rubis et latéraux sont écrits dans des balises html dans le texte. Utilisez HTMLParser pour analyser cela. J'ai l'impression que l'une des bibliothèques peut gérer les deux, mais j'ai décidé d'utiliser les deux pour étudier. Utilisez Pillow, une bibliothèque de traitement d'image, pour dessiner des personnages. Bien sûr, cela ne peut pas être aidé, mais Pillow ne prend pas du tout en charge l'écriture verticale, donc je fais beaucoup d'essais et d'erreurs (découpage) pour écrire verticalement.

Disposition et corps

Le xml qui détermine la mise en page et le corps du texte ressemble à ce qui suit.

yosuruni_layout.xml


<novel width="1920" height="960" margin_up="0.1" margin_bottom="0.1" margin_left="0.05" margin_right="0.05">
    <columnchain name="Main" fontsize="36" direction="VERTICAL" linespace="2.0" color="#101000">
        <column refp="UP_RIGHT" reflh="MARGIN_RIGHT" reflv="MARGIN_UP" offsetx="LIVEAREA_H:0" offsety="LIVEAREA_V:0" sizew="LIVEAREA_H:1.0" sizeh="LIVEAREA_V:1.0"/>
    </columnchain>
    <text columnchain="Main" src="yosuruni.xml" />
</novel>

yosuruni.xml


Mon enfant, qui avait quatre ans, est devenu assez lisible. Je pense lire beaucoup de livres à partir de maintenant, mais je n'ai pas encore beaucoup lu, donc je ne savais pas quel genre de livre choisir.

Suite à diverses réflexions, j'ai réalisé que je devais écrire moi-même une histoire et la faire lire. Même si je n'ai pas lu de livre en premier lieu, je me demande si je peux écrire une histoire, mais il n'y a aucun doute si j'écris "Oshiri" ou "Unko" correctement. Parce que j'ai 4 ans.

Au fait, quand j'ai commencé à écrire avec une application de bloc-notes, c'était une écriture complètement horizontale<d>Pas comme</d>.. S'il n'est pas écrit verticalement, comme "matériel de lecture"<r val="Amusement">atmosphère</r>Je ne peux pas le sentir et ce n'est pas amusant à regarder. J'ai donc cherché un éditeur capable d'écrire verticalement, mais je n'ai rien trouvé qui ressemblait à ça.

Si cela se produit, il n'y a pas d'autre choix que de le faire. Une application comme un éditeur est impossible, mais je pense que cela peut être fait avec un outil qui convertit du texte brut en une image verticale ...

Dans la mise en page, si vous augmentez les éléments de colonne dans l'élément de chaîne de colonnes, les colonnes augmenteront et le texte sera réparti dans l'ordre de la description de la colonne.

La mise en page et le corps du texte sont essentiellement analysés par les routines suivantes.

Analyse de la mise en page.py


import xml.etree.ElementTree as ET

#Analyser xml pour obtenir l'arborescence des éléments
tree = ET.parse(xml_path)
#Obtenez l'élément racine. Vous obtenez un élément nouveau
novel_element = tree.getroot()

#Ici, reportez-vous aux attributs du nouvel élément pour obtenir la valeur de réglage.

#Obtenez l'élément columnchain à l'intérieur du nouvel élément
for cc_element in novel_element.iter("columnchain"):
    #Ici, reportez-vous à l'attribut de l'élément columnchain pour obtenir la valeur du paramètre.

    #Obtenez l'élément de colonne à l'intérieur de l'élément columnchain
    for c_element in cc_element.iter("column"):
        #Ici, reportez-vous à l'attribut de l'élément columnc pour obtenir la valeur du paramètre.

#Obtenez l'élément de texte à l'intérieur de l'élément roman
for text_element in novel_element.iter("text"):
    #Ici, reportez-vous à l'attribut de l'élément de texte pour obtenir la valeur de réglage.

Analyse corporelle.py


from html.parser import HTMLParser

class TextParser(HTMLParser):

    def __init__(self):
        super().__init__()

    def handle_starttag(self, tag, attrs):
        if tag == "ruby" or tag=="r":
            #Détecter le début de la balise ruby

        if tag == "dot" or tag=="d":
            #Détecte le début d'une balise de point latéral

    def handle_endtag(self, tag):
        if tag == "ruby" or tag == "dot" or tag == "r" or tag == "d":
            #Détecter la fin du tag

    def handle_data(self, data):
        #Acquisition de données dans les balises. Le texte lui-même ou ruby(Lecture du pseudonyme)Avoir

class Text():

    def __init__(self, source):
        parser = TextParser()
        parser.feed(source)

Résultat d'exécution et de sortie

Par exemple, ce qui suit est une mise en page verticalement longue avec 3 lignes. (Comme il s'agit d'un exemple, la résolution est abaissée. Les caractères font 12 points à 320x720)

yosuruni_layout.xml


<novel width="320" height="720" margin_up="0.1" margin_bottom="0.1" margin_left="0.05" margin_right="0.05">
    <columnchain name="Main" fontsize="12" direction="VERTICAL" linespace="2.0" color="#101000">
        <column refp="UP_RIGHT" reflh="MARGIN_RIGHT" reflv="MARGIN_UP" offsetx="LIVEAREA_H:0" offsety="LIVEAREA_V:0" sizew="LIVEAREA_H:1.0" sizeh="LIVEAREA_V:0.3"/>
        <column refp="UP_RIGHT" reflh="MARGIN_RIGHT" reflv="MARGIN_UP" offsetx="LIVEAREA_H:0" offsety="LIVEAREA_V:0.35" sizew="LIVEAREA_H:1.0" sizeh="LIVEAREA_V:0.3"/>
        <column refp="UP_RIGHT" reflh="MARGIN_RIGHT" reflv="MARGIN_UP" offsetx="LIVEAREA_H:0" offsety="LIVEAREA_V:0.7" sizew="LIVEAREA_H:1.0" sizeh="LIVEAREA_V:0.3"/>
    </columnchain>
    <text columnchain="Main" src="yosuruni.xml" />
</novel>
$ python NovelFE.py yosuruni_layout.xml

Cela produira l'image suivante.

test0.png

Si la résolution est faible, la position des caractères fluctuera légèrement, ce qui est ennuyeux.

Dessin vertical

Comme mentionné ci-dessus, Pillow permet l'écriture horizontale, mais pas l'écriture verticale. J'ai donc décidé d'écrire verticalement. En d'autres termes, il dessine caractère par caractère tout en décalant la position dans le sens vertical.

J'ai utilisé Gensho Antic comme police. Une police de dessin animé qui prend en charge l'écriture verticale.

Cependant, quand j'essaye de le dessiner, c'est naturel,

ng.png

Les glyphes de police d'écriture horizontale tels que les parenthèses, les signes de ponctuation et les petits «tsu» sont utilisés. Dans la bibliothèque Pillow, il n'y a aucun moyen de spécifier l'écriture verticale, donc elle n'utilise tout simplement pas de glyphes pour l'écriture verticale.

Comme idée rapide et rapide de ce qu'il faut faire, j'ai eu l'idée de falsifier le fichier de police lui-même et de remplacer de force les glyphes horizontaux par des glyphes verticaux.

pip install fonttools

Installez les outils de polices Python fonttools (ttx). Si vous spécifiez un fichier de police avec la commande ttx, il sera converti en xml.

% ttx GenEiAntiqueN-Medium.otf 
Dumping "GenEiAntiqueN-Medium.otf" to "GenEiAntiqueN-Medium.ttx"...
Dumping 'GlyphOrder' table...
Dumping 'head' table...
Dumping 'hhea' table...
Dumping 'maxp' table...
Dumping 'OS/2' table...
Dumping 'name' table...
Dumping 'cmap' table...
Dumping 'post' table...
Dumping 'CFF ' table...
Dumping 'BASE' table...
Dumping 'GDEF' table...
Dumping 'GPOS' table...
Dumping 'GSUB' table...
Dumping 'VORG' table...
Dumping 'hmtx' table...
Dumping 'vhea' table...
Dumping 'vmtx' table...

La police utilisée cette fois-ci est au format OpenType, vérifiez donc le contour des spécifications ci-dessous.

Introduction aux spécifications OpenType (partie 2) [Introduction aux spécifications OpenType (partie 2)] (http://qiita.com/496_/items/4f8327fe741cf0c87736) [Introduction aux spécifications OpenType (partie 1)] (http://qiita.com/496_/items/f6efb650dcf7e9d2dfe4)

D'après ce qui précède, les fichiers XML importants générés par ttx sont GSUB et cmap.

OpenType contient à peu près des données de glyphes (polices) avec des identifiants, La table cmap contient une table de correspondance montrant les ID de données glyphes qui correspondent aux codes de caractère (par exemple Unicode). De plus, dans la table GSUB, si le glyphe utilisé dans une condition spécifique change, la table de correspondance entre l'ID de glyphe source de modification et l'ID de glyphe de destination de modification est affichée.

Par conséquent, la table de correspondance montrant l'ID de glyphe à remplacer dans le cas d'une écriture verticale est extraite de la table GSUB, et sur cette base, l'ID de glyphe de la table de correspondance dans la table cmap est remplacée. Ensuite, vous devriez pouvoir vous référer au glyphe pour l'écriture verticale sans condition.

Écrivons un script pour la conversion.

otfconv.py


import argparse
import xml.etree.ElementTree as ET

parser = argparse.ArgumentParser()
parser.add_argument("infile")
args = parser.parse_args()

tree = ET.parse(args.infile)
root = tree.getroot()

list_index = []
cid_replace_dic = {}

for gsub_elements in root.iter('GSUB'):
    for featurerecords in gsub_elements.iter('FeatureRecord'):
        for featuretags in featurerecords.iter('FeatureTag'):
            if featuretags.attrib['value'] == "vert" or \
                    featuretags.attrib['value'] == "vrt2" or \
                    featuretags.attrib['value'] == "vtrt":
                for lookuplistindexs in featurerecords.iter('LookupListIndex'):
                    if not lookuplistindexs.get('value') in list_index:
                        list_index.append(lookuplistindexs.get('value'))

    for lookup in gsub_elements.iter('Lookup'):
        if lookup.get('index') in list_index:
            for substitution in lookup.iter('Substitution'):
                cid_replace_dic[substitution.get('in')] = substitution.get('out')


for cmap in root.iter('cmap'):
    for maps in cmap.iter('map'):
        if maps.get('name') in cid_replace_dic.keys():
            maps.set('name', cid_replace_dic[maps.get('name')])

tree.write("output.xml")
$ python otfconv.py GenEiAntiqueN-Medium.ttx

Espérons que output.xml sera créé et je le reconvertirai en fichier OpenType avec ttx. Au fait, quand je l'ai fait, j'ai eu une erreur de conversion à moins que j'aie ajouté la ligne suivante au début. (Cela a fonctionné, donc je n'ai pas examiné trop de détails.)

<?xml version="1.0" encoding="UTF-8"?>

Utilisez ttx pour revenir de xml à otf.

$ ttx -o TateFont.otf output.xml

Puis

test.png

J'ai pu écrire comme ça.

Quelques fonctionnalités requises

Je pensais qu'au moins un traitement de rubis et d'interdiction était nécessaire pour en faire un roman. De plus, en prime, c'est un point secondaire. Rubi est un peu gênant car si la hauteur des caractères du rubi dépasse la hauteur des caractères correspondante du texte, l'espace entre les caractères dans le texte doit être augmenté.

test0.png

L'autre est le support multi-pages. Si le texte ne tient pas sur une page, essayez de générer plusieurs images avec la même mise en page.

Vous êtes maintenant prêt à écrire une histoire.

à la fin

Recommended Posts

Générer une image verticale d'un roman à partir de données textuelles
Générer un dictionnaire MeCab à partir des données de l'Encyclopédie Nico Nico
Texte extrait de l'image
Générer du texte d'image ensemble
Créer un bloc de données à partir des données textuelles de course de bateaux acquises
[Python] Extrayez des données texte à partir de données XML de 10 Go ou plus.
J'ai essayé d'utiliser PI Fu pour générer un modèle 3D d'une personne à partir d'une image
Générer une image Docker à l'aide de Fabric
Créer un identifiant Santa à partir d'une image du Père Noël
Générer automatiquement un collage à partir de la liste d'images
[Spark Data Frame] Changer une colonne de l'horizontale à la verticale (Scala)
J'ai créé un fichier de sous-titres (SRT) à partir des données JSON d'AmiVoice
Générer une liste de caractères consécutifs
Comment envoyer une image visualisée des données créées en Python à Typetalk
L'histoire d'une personne qui a commencé à viser un data scientist depuis un débutant
python + faker Générer aléatoirement un point avec un rayon de 100m à partir d'un certain point
Acquisition des données de croissance des plantes Acquisition des données des capteurs
Générer une classe à partir d'une chaîne en Python
Un mémorandum de problème lors du formatage des données
"Minecraft où le yaji vole" Générez du texte approprié avec Deep Learning ~ Collectez des données ~
J'ai essayé de générer automatiquement une table de gestion des ports à partir de L2SW Config
Un mémo pour générer des variables dynamiques de classe à partir de données de dictionnaire (dict) qui n'ont que des données de type standard en Python3
Générer et publier des données d'image factice avec Django
Télécharger des images à partir d'un fichier texte contenant l'URL
Un mémorandum sur l'appel de Python à partir de Common Lisp
Comment générer un objet Python à partir de JSON
Détecter les données General MIDI à partir d'une grande quantité de MIDI
Nettoyage des données 3 Utilisation d'OpenCV et prétraitement des données d'image
Environnement enregistré pour l'analyse des données avec Python
Transition du baseball vue à partir des données
Extraire des données d'une page Web avec Python
Essayez de créer une table d'enregistrement de bataille avec matplotlib à partir des données de "Schedule-kun"
Obtenez une liste des comptes GA, des propriétés et des vues sous forme de données verticales à l'aide de l'API
Découvrez le nombre maximum de caractères dans un texte multiligne stocké dans un bloc de données