listings / GimpPythonFuVerticalWriting-2-8.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
########################################################################
# version 1.0.0
#
# GimpPythonFuVerticalWriting-2-8.py
# Copyright (C) 2013 かんら・から http://www.pixiv.net/member.php?id=3098715
#
# GimpPythonFuVerticalWriting-2-8.py is Python-fu plugin for GIMP 2.8
#
# GimpPythonFuVerticalWriting-2-8.py 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.
#  
# GimpPythonFuVerticalWriting-2-8.py 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 this program.  If not, see <http://www.gnu.org/licenses/>.
#
# GPLv3 ライセンス
# かんら・から http://www.pixiv.net/member.php?id=3098715
# バグレポート・アイデアなどは pixiv メッセージでお願いします。
#
# ダウンロード
# http://www.magic-object.mydns.jp/
#
# このスクリプトを使用して発生した問題に関し、作成者は如何なる保証などを行う事はありません。
# 自己責任でのみの使用を許可します。
########################################################################
from gimpfu import *
import gimpcolor
import gimpui
import gtk
import gobject
import re
import math
import xml.dom.minidom
# import chardet
import os
import ConfigParser
import pygtk
pygtk.require('2.0')
########################################################################
########################################################################
# Pangoクラス
#
# Pango を操作する
#
########################################################################
class PangoAccess:
    ########################################################################
    document = None
    fontList = []
    lines = []
    ########################################################################
    defaultFont = 'Sans'
    ########################################################################
    def __init__(self, markUp, defaultText=None):
        if markUp:
            markUp = markUp.rstrip()
            try:
                self.document = xml.dom.minidom.parseString(markUp)
            except:
                self.document = xml.dom.minidom.parseString('<markup></markup>')
                if defaultText:
                    defaultText = defaultText.rstrip()
                    textNode = self.document.createTextNode(defaultText)
                    self.document.firstChild.appendChild(textNode)
                elif markUp:
                    textNode = self.document.createTextNode(markUp)
                    self.document.firstChild.appendChild(textNode)
        else:
            self.document = xml.dom.minidom.parseString('<markup></markup>')
            if defaultText:
                textNode = self.document.createTextNode(defaultText)
                self.document.firstChild.appendChild(textNode)
        self.parse()
    ########################################################################
    def __del__(self):
        if self.document:
            self.document.unlink()
    ########################################################################
    def getAllText(self):
        if not self.document:
            return ''
        ########################################################################
        return self.getTextNodeValue(self.document.firstChild)
    ########################################################################
    def toUnicode(self, text):
        if isinstance(text, unicode):
            return text
        if not isinstance(text, str):
            text = str(text)
        # codecInfo = chardet.detect( text )
        # return unicode( text, codecInfo['encoding'] )
        try:
            return unicode(text, 'utf-8')
        except:
            pass
        try:
            return unicode(text, 'euc-jp')
        except:
            pass
        try:
            return unicode(text, 'cp932')
        except:
            return text
    ########################################################################
    def getTextNodeValue(self, element):
        if not element:
            return ''
        elif element.nodeType == element.TEXT_NODE:
            return element.nodeValue
        elif element.nodeType != element.ELEMENT_NODE:
            return ''
        ########################################################################
        text = ''
        for node in element.childNodes:
            if node.hasChildNodes():
                text += self.getTextNodeValue(node)
            elif node.nodeType == node.TEXT_NODE:
                text += node.nodeValue
        ########################################################################
        return text
    ########################################################################
    def setFontList(self, fontList):
        self.fontList = sorted(fontList)
    ########################################################################
    def getFontName(self, name, bold=False, italic=False):
        if name not in self.fontList:
            return name
        ########################################################################
        regExpBoldItalic = re.compile('( bold italic$)|( italic bold$)|(-P?BI?$)', re.IGNORECASE)
        regExpBold = re.compile('( bold$)|(-P?B$)', re.IGNORECASE)
        regExpItalic = re.compile('( italic$)|(-I$)', re.IGNORECASE)
        if bold and italic:
            if regExpBoldItalic.search(name):
                return name
        elif bold:
            if regExpBold.search(name):
                return name
        elif italic:
            if regExpItalic.search(name):
                return name
        ########################################################################
        orgFontName = re.sub('-[PBSI]+$', '', name)
        regExpBoldItalic = re.compile('^(' + re.escape(orgFontName) + '\\s*)(' + regExpBoldItalic.pattern + ')',
                                      regExpBoldItalic.flags)
        regExpBold = re.compile('^(' + re.escape(orgFontName) + '\\s*)(' + regExpBold.pattern + ')', regExpBold.flags)
        regExpItalic = re.compile('^(' + re.escape(orgFontName) + '\\s*)(' + regExpItalic.pattern + ')',
                                  regExpItalic.flags)
        ########################################################################
        for fontName in self.fontList:
            if bold and italic:
                if regExpBoldItalic.search(fontName):
                    return fontName
            elif bold:
                if regExpBold.search(fontName):
                    return fontName
            elif italic:
                if regExpItalic.search(fontName):
                    return fontName
        ########################################################################
        return name
    ########################################################################
    def setDefaultFont(self, font):
        self.defaultFont = font
    ########################################################################
    def parse(self):
        self.lines = []
        if not self.document:
            return self.lines
        ########################################################################
        nodeList = self.document.firstChild.childNodes
        for node in nodeList:
            self.parseNode(node, self.defaultFont, False, False, {})
    ########################################################################
    def parseNode(self, node, font=None, bold=False, italic=False, attribute={}):
        if not node:
            return
        if not isinstance(node, xml.dom.minidom.Node):
            return
        if node.nodeType == node.TEXT_NODE:
            self.wordAdd(node.nodeValue, font, bold, italic, attribute)
            return
        elif node.nodeType == node.ELEMENT_NODE:
            if not node.hasChildNodes():
                return
            elif node.tagName.lower() == 'b':
                bold = True
            elif node.tagName.lower() == 'i':
                italic = True
            elif node.tagName.lower() == 's':
                attribute['strikethrough'] = True
            elif node.tagName.lower() == 'u':
                attribute['underline'] = 'single'
            elif node.tagName.lower() == 'span':
                if node.attributes:
                    for index in range(node.attributes.length):
                        attrNode = node.attributes.item(index)
                        if attrNode.name.lower() in ('font', 'font_desc'):
                            font = attrNode.nodeValue
                        elif attrNode.name.lower() in ('size', 'font_size'):
                            if attrNode.nodeValue.isdigit():
                                attribute['point'] = int(attrNode.nodeValue) / 1024.0
                            elif attrNode.name.lower() == 'xx-large':
                                attribute['point'] = 32
                            elif attrNode.name.lower() == 'x-large':
                                attribute['point'] = 18
                            elif attrNode.name.lower() == 'large':
                                attribute['point'] = 19.2
                            elif attrNode.name.lower() == 'midium':
                                attribute['point'] = 16
                            elif attrNode.name.lower() == 'small':
                                attribute['point'] = 14.2
                            elif attrNode.name.lower() == 'x-small':
                                attribute['point'] = 12
                            elif attrNode.name.lower() == 'xx-small':
                                attribute['point'] = 9.6
                            else:
                                attribute['size'] = attrNode.nodeValue
                        elif attrNode.name.lower() in ('foreground', 'fgcolor', 'color'):
                            color = {'red': 0.0, 'green': 0.0, 'blue': 0.0}
                            if attrNode.nodeValue.startswith('#'):
                                match = re.match('^#([0-9A-Fa-f]{2})([0-9A-Fa-f]{2})([0-9A-Fa-f]{2})$',
                                                 attrNode.nodeValue)
                                if match:
                                    color['red'] = int(match.group(1), 16) / 255.0
                                    color['green'] = int(match.group(2), 16) / 255.0
                                    color['blue'] = int(match.group(3), 16) / 255.0
                                elif re.match('^#([0-9A-Fa-f])([0-9A-Fa-f])([0-9A-Fa-f])$', attrNode.nodeValue):
                                    match = re.match('^#([0-9A-Fa-f])([0-9A-Fa-f])([0-9A-Fa-f])$', attrNode.nodeValue)
                                    color['red'] = int(match.group(1), 16) / 15.0
                                    color['green'] = int(match.group(2), 16) / 15.0
                                    color['blue'] = int(match.group(3), 16) / 15.0
                            elif attrNode.nodeValue.lower() == 'white':
                                color['red'] = 1.0
                                color['green'] = 1.0
                                color['blue'] = 1.0
                            elif attrNode.nodeValue.lower() == 'black':
                                color['red'] = 0.0
                                color['green'] = 0.0
                                color['blue'] = 0.0
                            elif attrNode.nodeValue.lower() == 'gray':
                                color['red'] = 0.5
                                color['green'] = 0.5
                                color['blue'] = 0.5
                            elif attrNode.nodeValue.lower() == 'red':
                                color['red'] = 1.0
                                color['green'] = 0.0
                                color['blue'] = 0.0
                            elif attrNode.nodeValue.lower() == 'green':
                                color['red'] = 0.0
                                color['green'] = 1.0
                                color['blue'] = 0.0
                            elif attrNode.nodeValue.lower() == 'blue':
                                color['red'] = 0.0
                                color['green'] = 0.0
                                color['blue'] = 1.0
                            elif attrNode.nodeValue.lower() == 'cyan':
                                color['red'] = 0.0
                                color['green'] = 1.0
                                color['blue'] = 1.0
                            elif attrNode.nodeValue.lower() == 'magenta':
                                color['red'] = 1.0
                                color['green'] = 0.0
                                color['blue'] = 1.0
                            elif attrNode.nodeValue.lower() == 'yellow':
                                color['red'] = 1.0
                                color['green'] = 1.0
                                color['blue'] = 0.0
                            attribute['color'] = color
                        else:
                            attribute[attrNode.name.lower()] = attrNode.nodeValue.lower()
            for child in node.childNodes:
                self.parseNode(child, font, bold, italic, attribute)
            return
    ########################################################################
    ########################################################################
    def wordAdd(self, text, font=None, bold=False, italic=False, attribute={}):
        ########################################################################
        if re.search('\\r?\\n', text):
            lfCount = len(re.findall('\\r?\\n', text))
            restLf = lfCount
            for line in re.split('\\r?\\n', text):
                self.wordAdd(line, font, bold, italic, attribute)
                if restLf > 0:
                    self.lines.append([])
                    restLf -= 1
            return
        ########################################################################
        if self.lines is None or not isinstance(self.lines, list):
            self.lines = [[]]
        ########################################################################
        lineIndex = len(self.lines) - 1
        if lineIndex < 0:
            self.lines.append([])
            lineIndex = 0
        elif self.lines[lineIndex] is None or not isinstance(self.lines[lineIndex], list):
            self.lines[lineIndex] = []
        ########################################################################
        wordData = {
            'font': self.getFontName(font, bold, italic),
            'text': self.toUnicode(text)
        }
        if isinstance(attribute, dict):
            for key in attribute.keys():
                if key not in wordData.keys():
                    wordData[key] = attribute[key]
        self.lines[lineIndex].append(wordData)
########################################################################
########################################################################
# Preference クラス
#
# GIMP の初期設定ファイルを操作する
#
########################################################################
class GimpPreference:
    ########################################################################
    gimpPreferenceDirPath = None
    preferenceDirPath = None
    preferenceFilePath = None
    ########################################################################
    config = None
    ########################################################################
    def __init__(self, majorVersion, minorVersion, dirName, fileName):
        homePath = os.path.expanduser('~')
        gimpPreferenceDirName = '.gimp-' + str(majorVersion) + '.' + str(minorVersion)
        self.gimpPreferenceDirPath = os.path.join(homePath, gimpPreferenceDirName)
        if not os.path.exists(self.gimpPreferenceDirPath):
            return
        self.preferenceDirPath = os.path.join(self.gimpPreferenceDirPath, dirName)
        if not os.path.exists(self.preferenceDirPath):
            os.mkdir(self.preferenceDirPath)
        self.preferenceFilePath = os.path.join(self.preferenceDirPath, fileName)
    ########################################################################
    def __del__(self):
        pass
    ########################################################################
    def load(self):
        if not os.path.exists(self.preferenceFilePath):
            return False
        self.config = ConfigParser.SafeConfigParser(allow_no_value=True)
        try:
            self.config.read(self.preferenceFilePath)
            return True
        except:
            return False
    ########################################################################
    def save(self):
        if not self.preferenceFilePath:
            return False
        if not self.config:
            return False
        try:
            with open(self.preferenceFilePath, 'wb') as configfile:
                self.config.write(configfile)
            return True
        except:
            return False
    ########################################################################
    def getAll(self, section):
        if not section:
            return {}
        if not self.config:
            return {}
        try:
            results = {}
            pairList = self.config.items(section)
            for pair in pairList:
                results[pair[0]] = pair[1]
            return pair
        except:
            return {}
    ########################################################################
    def getOne(self, section, option, defaultValue=None, isString=False):
        if not section:
            return defaultValue
        if not option:
            return defaultValue
        if not self.config:
            return defaultValue
        try:
            value = self.config.get(section, option, isString)
            if isString:
                return value
            elif value.lower() == 'true':
                return True
            elif value.lower() == 'false':
                return False
            else:
                try:
                    if int(value) == float(value):
                        return int(value)
                    else:
                        return float(value)
                except:
                    return value
        except:
            return defaultValue
    ########################################################################
    def setOne(self, section, option, value):
        if not section:
            return False
        if not option:
            return False
        if not self.config:
            self.config = ConfigParser.SafeConfigParser(allow_no_value=True)
            if not self.config:
                return False
        if value is None:
            value = ''
        elif isinstance(value, bool):
            if value:
                value = '1'
            else:
                value = '0'
        elif not isinstance(value, unicode) and not isinstance(value, str):
            value = str(value)
        try:
            self.config.add_section(section)
        except:
            pass
        try:
            self.config.set(section, option, value)
            return True
        except:
            return False
########################################################################
########################################################################
# 縦書クラス情報取得
#
# テキストレイヤーから縦書きレイヤー用の情報を入手するダイアログ
#
########################################################################
class VerticalTextAccess:
    ########################################################################
    (
        _LineNumber,
        _ColumnNumber,
        _RubyTargetNumcer,
        _RubyNumber
    ) = range(4)
    ########################################################################
    image = None
    targetTextLayer = None
    doPutMessage = True
    lastMessage = ''
    usableVersion = (2, 8, 0)
    canUseLayerGroup = False
    pango = None
    ########################################################################
    preferenceDirName = 'VerticalWriting'
    preferenceFileName = 'preference.conf'
    ########################################################################
    lineSpaceRate = 1.0
    charSpaceRate = 0.0
    rubySizeRate = 1.0 / 3.0
    rightShiftRate = 0.5
    upShiftRate = 0.5
    forceSmallerScaleRate = 0.8
    ########################################################################
    useSizeBool = True
    useColorBool = True
    useRubyBool = True
    ########################################################################
    defaultRotateRightTartget = u'[]{}()<>|-~_|「」『』(){}【】<>《》〔〕〘〙〚〛‥…ー−―〜~'
    defaultRightShiftTarget = u'\'"、。’”'
    defaultUpShiftTarget = u'、。'
    defaultJoinBeforeTarget = u'゜゛'
    defaultConsiderOneCharTarget = u'!?'
    defaultForceSmallerTarget = u'ぁぃぅぇぉゃゅょっゎァィゥェォヵヶャュョッヮ'
    ########################################################################
    rotateRightTartget = defaultRotateRightTartget
    rightShiftTarget = defaultRightShiftTarget
    upShiftTarget = defaultUpShiftTarget
    joinBeforeTarget = defaultJoinBeforeTarget
    considerOneCharTarget = defaultConsiderOneCharTarget
    forceSmallerTarget = defaultForceSmallerTarget
    flags = {}
    ########################################################################
    def __init__(self, image):
        self.results = gtk.RESPONSE_CANCEL
        self.image = image
        versionLength = len(self.usableVersion)
        for index in range(0, versionLength):
            if self.usableVersion[index] < gimp.version[index]:
                self.canUseLayerGroup = True
                break
            elif self.usableVersion[index] == gimp.version[index]:
                if index == (versionLength - 1):
                    self.canUseLayerGroup = True
                    break
                continue
            else:
                self.canUseLayerGroup = False
                break
        return
    ########################################################################
    def checkUsable(self):
        if not self.canUseLayerGroup:
            self.errorMessage('GIMP的版本太旧了。')
            return False
        else:
            layer = pdb.gimp_image_get_active_layer(self.image)
            if not pdb.gimp_item_is_text_layer(layer):
                self.errorMessage('文本图层未被选中。')
                return False
            else:
                return True
    ########################################################################
    # エラーメッセージ機能
    #
    def errorMessage(self, message=None):
        if self.doPutMessage:
            if message is None:
                message = self.lastMessage
            else:
                self.lastMessage = message
            print message
            pdb.gimp_message_set_handler(MESSAGE_BOX)
            pdb.gimp_message(message)
        # MESSAGE_BOX CONSOLE ERROR_CONSOLE
        # gimp.message( message )
        return
    ########################################################################
    def getLayerText(self):
        if not self.image:
            return ''
        layer = pdb.gimp_image_get_active_layer(self.image)
        if not pdb.gimp_item_is_text_layer(layer):
            return ''
        self.targetTextLayer = layer
        markUp = pdb.gimp_text_layer_get_markup(layer)
        if markUp:
            self.pango = PangoAccess(markUp)
            return self.pango.getAllText()
        text = pdb.gimp_text_layer_get_text(layer)
        if text:
            self.pango = PangoAccess(text)
            return text
        return ''
    ########################################################################
    def dialogMake(self, proc_name=None, defaultText=None):
        self.loadPreference()
        self.dialog = gimpui.Dialog(proc_name, "python-fu", None, 0, None, proc_name,
                                    (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OK, gtk.RESPONSE_OK)
                                    )
        self.dialog.set_modal(True)
        self.dialog.set_alternative_button_order((gtk.RESPONSE_OK, gtk.RESPONSE_CANCEL))
        # self.dialog.se_transient()
        self.tooltips = gtk.Tooltips()
        hBox = gtk.HBox()
        label = gtk.Label('行间距(pt):')
        hBox.pack_start(label, expand=False)
        self.lineSpacePointSpin = gtk.SpinButton()
        self.lineSpacePointSpin.set_digits(1)
        self.lineSpacePointSpin.set_range(1, 72)
        self.lineSpacePointSpin.set_increments(1, 10)
        self.setDafultLineSpace()
        hBox.pack_start(self.lineSpacePointSpin, expand=False)
        self.tooltips.set_tip(self.lineSpacePointSpin, '请指定行之间的间距。')
        label = gtk.Label(' 文字间距(pt):')
        hBox.pack_start(label, expand=False)
        self.charSpacePointSpin = gtk.SpinButton()
        self.charSpacePointSpin.set_digits(1)
        self.charSpacePointSpin.set_range(0, 72)
        self.charSpacePointSpin.set_increments(1, 10)
        self.setDafultCharSpace()
        hBox.pack_start(self.charSpacePointSpin, expand=False)
        self.tooltips.set_tip(self.charSpacePointSpin, '请指定文字之间的间距。')
        self.dialog.vbox.pack_start(hBox, expand=False)
        label = gtk.Label('')
        hBox.pack_start(label, expand=True)
        self.resetButton = gtk.Button('重置')
        self.resetButton.connect('clicked', self.clickResetButton, None)
        hBox.pack_start(self.resetButton, expand=False)
        self.tooltips.set_tip(self.resetButton, '重置除ruby项目之外的所有项目。')
        self.useSizeCheckButton = gtk.CheckButton('使用单独字体大小信息')
        self.useSizeCheckButton.set_active(self.useSizeBool)
        self.useSizeCheckButton.show()
        self.dialog.vbox.pack_start(self.useSizeCheckButton, expand=False)
        self.tooltips.set_tip(self.useSizeCheckButton, '每个字符的大小不同。')
        self.useColorCheckButton = gtk.CheckButton('使用单独的字体颜色信息')
        self.useColorCheckButton.set_active(self.useColorBool)
        self.useColorCheckButton.show()
        self.dialog.vbox.pack_start(self.useColorCheckButton, expand=False)
        self.tooltips.set_tip(self.useColorCheckButton, '每个字符的颜色不同。')
        self.useRubyCheckButton = gtk.CheckButton('使用ruby')
        self.useRubyCheckButton.set_active(self.useRubyBool)
        self.useRubyCheckButton.show()
        self.useRubyCheckButton.connect('toggled', self.tggleUseRubyCheckButton, None)
        self.dialog.vbox.pack_start(self.useRubyCheckButton, expand=False)
        self.tooltips.set_tip(self.useRubyCheckButton, '请确定是否使用ruby。')
        self.rubyVBox = gtk.VBox()
        self.rubyVBox.show()
        self.rubyHBox = gtk.HBox()
        self.rubyHBox.show()
        self.rubyHBox.pack_start(self.rubyVBox, expand=True, fill=True, padding=0)
        ### テキスト表示エリア ###
        self.textView = gtk.TextView()
        self.textView.set_editable(False)
        textBuffer = self.textView.get_buffer()
        if defaultText:
            textBuffer.set_text(defaultText)
        else:
            layerText = self.getLayerText()
            if layerText:
                textBuffer.set_text(layerText)
        textBuffer.connect('notify::has-selection', self.notify_has_selection)
        textBuffer.connect_after('notify::cursor-position', self.notify_cursor_position)
        self.textView.connect('button-release-event', self.button_release_event)
        self.textView.connect('button-press-event', self.button_press_event)
        # self.textView.connect( 'move-cursor', self.textSelectChange, None )
        self.textView.show()
        self.tooltips.set_tip(self.textView, '选择想要添加ruby的文字,然后按“添加ruby”按扭。')
        self.textScrollArea = gtk.ScrolledWindow()
        self.textScrollArea.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
        self.textScrollArea.add(self.textView)
        self.textScrollArea.show()
        hSeparator = gtk.HSeparator()
        hSeparator.show()
        # self.dialog.vbox.pack_start( self.textScrollArea )
        # self.dialog.vbox.pack_start( hSeparator )
        self.rubyVBox.pack_start(self.textScrollArea)
        self.rubyVBox.pack_start(hSeparator)
        ### ルビリストエリア ###
        self.rubyListStore = gtk.ListStore(gobject.TYPE_INT, gobject.TYPE_INT, gobject.TYPE_STRING, gobject.TYPE_STRING)
        self.listBox = gtk.TreeView(self.rubyListStore)
        self.listSelection = self.listBox.get_selection()
        self.listSelection.connect('changed', self.changedRubySlection, self.listBox)
        renderer = gtk.CellRendererText()
        column = gtk.TreeViewColumn('行数', renderer, text=self._LineNumber)
        column.set_sort_column_id(self._LineNumber)
        self.listBox.append_column(column)
        renderer = gtk.CellRendererText()
        column = gtk.TreeViewColumn('起始位置', renderer, text=self._ColumnNumber)
        column.set_sort_column_id(self._ColumnNumber)
        self.listBox.append_column(column)
        renderer = gtk.CellRendererText()
        column = gtk.TreeViewColumn('对象', renderer, text=self._RubyTargetNumcer)
        column.set_sort_column_id(self._RubyTargetNumcer)
        self.listBox.append_column(column)
        renderer = gtk.CellRendererText()
        renderer.set_property('editable', True)
        renderer.connect('edited', self.rubyChanged, self.rubyListStore, self._RubyNumber)
        column = gtk.TreeViewColumn('ruby', renderer, text=self._RubyNumber)
        column.set_sort_column_id(self._RubyNumber)
        self.listBox.append_column(column)
        self.listBox.show()
        self.tooltips.set_tip(self.listBox, '如果要更改ruby字符,请双击“对象”并更改它。')
        self.rubyScrollArea = gtk.ScrolledWindow()
        self.rubyScrollArea.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
        self.rubyScrollArea.add(self.listBox)
        self.rubyScrollArea.show()
        hBox = gtk.HBox()
        hBox.show()
        hBox.pack_start(self.rubyScrollArea)
        vBox = gtk.VBox()
        vBox.show()
        self.rubyAddButton = gtk.Button('添加ruby')
        self.rubyAddButton.set_sensitive(False)
        self.rubyAddButton.connect('clicked', self.clickAddRubyButton, None)
        self.rubyAddButton.show()
        vBox.pack_start(self.rubyAddButton, expand=False)
        self.tooltips.set_tip(self.rubyAddButton, '请从上方区域选择需要添加ruby的文字,然后按下此按扭。')
        self.rubyRemoveButton = gtk.Button('删除ruby')
        self.rubyRemoveButton.set_sensitive(False)
        self.rubyRemoveButton.connect('clicked', self.clickRemoveRubyButton, None)
        self.rubyRemoveButton.show()
        vBox.pack_start(self.rubyRemoveButton, expand=False)
        self.tooltips.set_tip(self.rubyRemoveButton, '请选择左档并按下此按扭。')
        hSeparator = gtk.HSeparator()
        hSeparator.show()
        vBox.pack_start(hSeparator, expand=True)
        ########################################################################
        label = gtk.Label('ruby大小(pt)')
        vBox.pack_start(label, expand=False)
        self.rubyPointSpin = gtk.SpinButton()
        self.rubyPointSpin.set_digits(1)
        self.rubyPointSpin.set_range(1, 100)
        self.rubyPointSpin.set_increments(1, 10)
        self.setDefaultRubySize()
        vBox.pack_start(self.rubyPointSpin, expand=False)
        self.tooltips.set_tip(self.rubyPointSpin, '请输入ruby字体大小。')
        hBox.pack_start(vBox, expand=False)
        ########################################################################
        self.rubyVBox.pack_start(hBox)
        self.rubyFrame = gtk.Frame('ruby')
        self.rubyFrame.show()
        self.rubyFrame.add(self.rubyHBox)
        self.dialog.vbox.pack_start(self.rubyFrame, padding=20)
        hBox = gtk.HBox()
        label = gtk.Label('向右旋转90°的字符')
        hBox.pack_start(label, expand=False)
        self.rotateRightLineEdit = gtk.Entry()
        self.rotateRightLineEdit.set_text(self.rotateRightTartget)
        hBox.pack_start(self.rotateRightLineEdit)
        self.dialog.vbox.pack_start(hBox)
        self.tooltips.set_tip(self.rotateRightLineEdit, '请指定在竖直文本下需要向右旋转90°的字符。')
        hBox = gtk.HBox()
        label = gtk.Label('向右侧移动的字符')
        hBox.pack_start(label, expand=False)
        self.rightShiftLineEdit = gtk.Entry()
        self.rightShiftLineEdit.set_text(self.rightShiftTarget)
        hBox.pack_start(self.rightShiftLineEdit)
        self.tooltips.set_tip(self.rightShiftLineEdit, '请指定在竖直文本下需要向右侧移动的字符。')
        label = gtk.Label(' 移动比率:')
        hBox.pack_start(label, expand=False)
        self.rightShiftScaleSpin = gtk.SpinButton()
        self.rightShiftScaleSpin.set_digits(2)
        self.rightShiftScaleSpin.set_range(0.05, 1)
        self.rightShiftScaleSpin.set_increments(0.05, 0.1)
        self.rightShiftScaleSpin.set_value(self.rightShiftRate)
        hBox.pack_start(self.rightShiftScaleSpin, expand=False)
        self.tooltips.set_tip(self.rightShiftScaleSpin, '请指定相对于字符宽度的移动比率。(0~1)')
        self.dialog.vbox.pack_start(hBox)
        hBox = gtk.HBox()
        label = gtk.Label('向上移动的字符')
        hBox.pack_start(label, expand=False)
        self.upShiftLineEdit = gtk.Entry()
        self.upShiftLineEdit.set_text(self.upShiftTarget)
        hBox.pack_start(self.upShiftLineEdit)
        self.tooltips.set_tip(self.upShiftLineEdit, '请指定在竖直文本下需要向上移动的字符。')
        label = gtk.Label(' 移动比率:')
        hBox.pack_start(label, expand=False)
        self.upShiftScaleSpin = gtk.SpinButton()
        self.upShiftScaleSpin.set_digits(2)
        self.upShiftScaleSpin.set_range(0.05, 1)
        self.upShiftScaleSpin.set_increments(0.05, 0.1)
        self.upShiftScaleSpin.set_value(self.upShiftRate)
        hBox.pack_start(self.upShiftScaleSpin, expand=False)
        self.tooltips.set_tip(self.upShiftScaleSpin, '请指定相对于字符高度的移动比率。(0~1)')
        self.dialog.vbox.pack_start(hBox)
        hBox = gtk.HBox()
        label = gtk.Label('与前一个字符结合的字符')
        hBox.pack_start(label, expand=False)
        self.joinBeforeLineEdit = gtk.Entry()
        self.joinBeforeLineEdit.set_text(self.joinBeforeTarget)
        hBox.pack_start(self.joinBeforeLineEdit)
        self.dialog.vbox.pack_start(hBox)
        self.tooltips.set_tip(self.joinBeforeLineEdit, '请指定您需要与前一个字符结合使用的内容,例如“゛”或“゜”。')
        hBox = gtk.HBox()
        label = gtk.Label('连续字符被识别为一个字符')
        hBox.pack_start(label, expand=False)
        self.considerOneCharLineEdit = gtk.Entry()
        self.considerOneCharLineEdit.set_text(self.considerOneCharTarget)
        hBox.pack_start(self.considerOneCharLineEdit)
        self.tooltips.set_tip(self.considerOneCharLineEdit, '请指定在竖直文本下想要识别的连续字符,如“!!?”。')
        self.dialog.vbox.pack_start(hBox)
        hBox = gtk.HBox()
        label = gtk.Label('被强制缩小的字符')
        hBox.pack_start(label, expand=False)
        self.forceSmallerTargetLineEdit = gtk.Entry()
        self.forceSmallerTargetLineEdit.set_text(self.forceSmallerTarget)
        hBox.pack_start(self.forceSmallerTargetLineEdit)
        self.tooltips.set_tip(self.forceSmallerTargetLineEdit, '请指定在竖直文本下略微缩小的字符,例如“っ”或“ぁ”。')
        label = gtk.Label(' 缩小比率:')
        hBox.pack_start(label, expand=False)
        self.forceSmallerScaleSpin = gtk.SpinButton()
        self.forceSmallerScaleSpin.set_digits(2)
        self.forceSmallerScaleSpin.set_range(0.05, 1)
        self.forceSmallerScaleSpin.set_increments(0.05, 0.1)
        self.forceSmallerScaleSpin.set_value(self.forceSmallerScaleRate)
        hBox.pack_start(self.forceSmallerScaleSpin, expand=False)
        self.tooltips.set_tip(self.forceSmallerScaleSpin, '请指定相对于字符大小的缩小比率。(0~1)')
        self.dialog.vbox.pack_start(hBox)
        self.dialog.set_default_size(700, 600)
        self.dialog.show_all()
    ########################################################################
    def notify_has_selection(self, buffer, *args):
        self.textViewSelectionCheck()
    ########################################################################
    def notify_cursor_position(self, buffer, position):
        self.textViewSelectionCheck()
    ########################################################################
    def button_release_event(self, textview, event):
        self.textViewSelectionCheck()
    ########################################################################
    def button_press_event(self, *args):
        self.textViewSelectionCheck()
    ########################################################################
    def textViewSelectionCheck(self):
        textbuffer = self.textView.get_buffer()
        selection = textbuffer.get_selection_bounds()
        if len(selection) < 2:
            self.rubyAddButton.set_sensitive(False)
            return
        else:
            text = textbuffer.get_text(selection[0], selection[1], include_hidden_chars=True)
            if re.match('^[^\\s]+[\\r]?[\\n].', text):
                self.rubyAddButton.set_sensitive(False)
            else:
                self.rubyAddButton.set_sensitive(True)
    ########################################################################
    def tggleUseRubyCheckButton(self, checkBox, data=None):
        if self.useRubyCheckButton.get_active():
            self.rubyFrame.show()
        else:
            self.rubyFrame.hide()
    ########################################################################
    def clickAddRubyButton(self, button, data=None):
        ########################################################################
        textbuffer = self.textView.get_buffer()
        selection = textbuffer.get_selection_bounds()
        if len(selection) < 2:
            return
        text = textbuffer.get_text(selection[0], selection[1], include_hidden_chars=True)
        if re.match('^[^\\s]+[\\r]?[\\n].', text):
            return
        ########################################################################
        lineNo = selection[0].get_line() + 1
        columnNo = selection[0].get_line_offset() + 1
        self.rubyListStore.append([lineNo, columnNo, text, '请在此输入ruby文本'])
    ########################################################################
    def rubyChanged(self, cellrenderertext, path, new_text, listStore, column):
        new_text = new_text.strip()
        if new_text:
            listStore[path][column] = new_text
    ########################################################################
    def changedRubySlection(self, treeselection, listBox):
        if treeselection.count_selected_rows() < 1:
            self.rubyRemoveButton.set_sensitive(False)
        else:
            self.rubyRemoveButton.set_sensitive(True)
    ########################################################################
    def clickRemoveRubyButton(self, button, data=None):
        if self.listSelection.count_selected_rows() < 1:
            return
        ########################################################################
        selected = self.listSelection.get_selected()
        if len(selected) < 2:
            return
        ########################################################################
        self.rubyListStore.remove(selected[1])
    ########################################################################
    def clickResetButton(self, button, data=None):
        self.resetValues()
        self.resetWidgetsValue()
    ########################################################################
    def resetValues(self):
        self.lineSpaceRate = 1.0
        self.charSpaceRate = 0.0
        self.rubySizeRate = 1.0 / 3.0
        self.rightShiftRate = 0.5
        self.upShiftRate = 0.5
        self.forceSmallerScaleRate = 0.8
        self.useSizeBool = True
        self.useColorBool = True
        self.useRubyBool = True
        self.rotateRightTartget = self.defaultRotateRightTartget
        self.rightShiftTarget = self.defaultRightShiftTarget
        self.upShiftTarget = self.defaultUpShiftTarget
        self.joinBeforeTarget = self.defaultJoinBeforeTarget
        self.considerOneCharTarget = self.defaultConsiderOneCharTarget
        self.forceSmallerTarget = self.defaultForceSmallerTarget
    ########################################################################
    def resetWidgetsValue(self):
        self.setDafultLineSpace()
        self.setDafultCharSpace()
        self.useSizeCheckButton.set_active(self.useSizeBool)
        self.useColorCheckButton.set_active(self.useColorBool)
        self.useRubyCheckButton.set_active(self.useRubyBool)
        self.setDefaultRubySize()
        self.rotateRightLineEdit.set_text(self.rotateRightTartget)
        self.rightShiftLineEdit.set_text(self.rightShiftTarget)
        self.rightShiftScaleSpin.set_value(self.rightShiftRate)
        self.upShiftLineEdit.set_text(self.upShiftTarget)
        self.upShiftScaleSpin.set_value(self.upShiftRate)
        self.joinBeforeLineEdit.set_text(self.joinBeforeTarget)
        self.considerOneCharLineEdit.set_text(self.considerOneCharTarget)
        self.forceSmallerTargetLineEdit.set_text(self.forceSmallerTarget)
        self.forceSmallerScaleSpin.set_value(self.forceSmallerScaleRate)
    ########################################################################
    def loadPreference(self):
        self.resetValues()
        pref = GimpPreference(gimp.version[0], gimp.version[1], self.preferenceDirName, self.preferenceFileName)
        if not pref.load():
            return
        self.lineSpaceRate = float(pref.getOne('space', 'lineSpaceRate', self.lineSpaceRate))
        self.charSpaceRate = float(pref.getOne('space', 'charSpaceRate', self.charSpaceRate))
        self.useSizeBool = bool(pref.getOne('bool', 'useSizeBool', self.useSizeBool))
        self.useColorBool = bool(pref.getOne('bool', 'useColorBool', self.useColorBool))
        self.useRubyBool = bool(pref.getOne('bool', 'useRubyBool', self.useRubyBool))
        self.rubySizeRate = float(pref.getOne('ruby', 'rubySizeRate', self.rubySizeRate))
        self.rightShiftRate = float(pref.getOne('shift', 'rightShiftRate', self.rightShiftRate))
        self.upShiftRate = float(pref.getOne('shift', 'upShiftRate', self.upShiftRate))
        self.forceSmallerScaleRate = float(pref.getOne('scale', 'forceSmallerScaleRate', self.forceSmallerScaleRate))
        dummy = PangoAccess(None)
        self.rotateRightTartget = dummy.toUnicode(
            pref.getOne('text', 'rotateRightTartget', self.rotateRightTartget, True))
        self.rightShiftTarget = dummy.toUnicode(pref.getOne('text', 'rightShiftTarget', self.rightShiftTarget, True))
        self.upShiftTarget = dummy.toUnicode(pref.getOne('text', 'upShiftTarget', self.upShiftTarget, True))
        self.joinBeforeTarget = dummy.toUnicode(pref.getOne('text', 'joinBeforeTarget', self.joinBeforeTarget, True))
        self.considerOneCharTarget = dummy.toUnicode(
            pref.getOne('text', 'considerOneCharTarget', self.considerOneCharTarget, True))
        self.forceSmallerTarget = dummy.toUnicode(
            pref.getOne('text', 'forceSmallerTarget', self.forceSmallerTarget, True))
    ########################################################################
    def savePreference(self):
        pointSize = self.getDefaultFontSizeInPoint()
        if pointSize <= 0.0:
            self.lineSpaceRate = 1.0
            self.charSpaceRate = 0.0
            self.rubySizeRate = 1.0 / 3.0
        else:
            self.lineSpaceRate = self.lineSpacePointSpin.get_value() / float(pointSize)
            self.charSpaceRate = self.charSpacePointSpin.get_value() / float(pointSize)
            self.rubySizeRate = self.rubyPointSpin.get_value() / float(pointSize)
        self.rightShiftRate = float(self.rightShiftScaleSpin.get_value())
        self.upShiftRate = float(self.upShiftScaleSpin.get_value())
        self.forceSmallerScaleRate = float(self.forceSmallerScaleSpin.get_value())
        pref = GimpPreference(gimp.version[0], gimp.version[1], self.preferenceDirName, self.preferenceFileName)
        if not pref.setOne('space', 'lineSpaceRate', self.lineSpaceRate):
            return False
        if not pref.setOne('space', 'charSpaceRate', self.charSpaceRate):
            return False
        if not pref.setOne('bool', 'useSizeBool', self.useSizeBool):
            return False
        if not pref.setOne('bool', 'useColorBool', self.useColorBool):
            return False
        if not pref.setOne('bool', 'useRubyBool', self.useRubyBool):
            return False
        if not pref.setOne('ruby', 'rubySizeRate', self.rubySizeRate):
            return False
        if not pref.setOne('shift', 'rightShiftRate', self.rightShiftRate):
            return False
        if not pref.setOne('shift', 'upShiftRate', self.upShiftRate):
            return False
        if not pref.setOne('scale', 'forceSmallerScaleRate', self.forceSmallerScaleRate):
            return False
        if not pref.setOne('text', 'rotateRightTartget', self.rotateRightTartget):
            return False
        if not pref.setOne('text', 'rightShiftTarget', self.rightShiftTarget):
            return False
        if not pref.setOne('text', 'upShiftTarget', self.upShiftTarget):
            return False
        if not pref.setOne('text', 'joinBeforeTarget', self.joinBeforeTarget):
            return False
        if not pref.setOne('text', 'considerOneCharTarget', self.considerOneCharTarget):
            return False
        if not pref.setOne('text', 'forceSmallerTarget', self.forceSmallerTarget):
            return False
        return pref.save()
    ########################################################################
    def setDafultLineSpace(self, defaultValue=12):
        try:
            pointSize = self.getDefaultFontSizeInPoint()
            if pointSize < 0:
                self.lineSpacePointSpin.set_value(defaultValue)
                return
            # 行間は文字のポイントサイズで
            self.lineSpacePointSpin.set_value(pointSize * self.lineSpaceRate)
        except:
            return
    ########################################################################
    def setDafultCharSpace(self, defaultValue=0):
        try:
            pointSize = self.getDefaultFontSizeInPoint()
            if pointSize < 0:
                self.charSpacePointSpin.set_value(defaultValue)
                return
            # 文字の間隔はポイントサイズで
            self.charSpacePointSpin.set_value(pointSize * self.charSpaceRate)
        except:
            return
    ########################################################################
    def getDefaultFontSizeInPoint(self):
        if not self.image:
            return -1.0
        try:
            layer = pdb.gimp_image_get_active_layer(self.image)
            if not pdb.gimp_item_is_text_layer(layer):
                return -1.0
            sizeInInch = 0
            (fontSize, fontUnit) = pdb.gimp_text_layer_get_font_size(layer)
            if pdb.gimp_unit_get_identifier(fontUnit).lower() in ['px', 'pix', 'pixel', 'pixels']:
                (xResolution, yResoLution) = pdb.gimp_image_get_resolution(self.image)
                sizeInInch = fontSize / xResolution
            else:
                sizeInInch = fontSize / pdb.gimp_unit_get_factor(fontUnit)
            point = sizeInInch * 72
            return point
        except:
            return -1.0
    ########################################################################
    def setDefaultRubySize(self, defaultValue=6):
        try:
            pointSize = self.getDefaultFontSizeInPoint()
            if pointSize < 0:
                self.rubyPointSpin.set_value(defaultValue)
                return
            # ルビは 1/3 サイズで
            self.rubyPointSpin.set_value(pointSize * self.rubySizeRate)
        except:
            return
    ########################################################################
    def setResults(self):
        self.flags['lineSpacePoint'] = self.lineSpacePointSpin.get_value()
        self.flags['charSpacePoint'] = self.charSpacePointSpin.get_value()
        self.flags['useFontSize'] = self.useSizeCheckButton.get_active()
        self.flags['useFontColor'] = self.useColorCheckButton.get_active()
        self.flags['useRuby'] = self.useRubyCheckButton.get_active()
        self.flags['rubyPointSize'] = self.rubyPointSpin.get_value()
        self.rotateRightTartget = self.pango.toUnicode(self.rotateRightLineEdit.get_text().strip())
        self.rightShiftTarget = self.pango.toUnicode(self.rightShiftLineEdit.get_text().strip())
        self.rightShiftScale = float(self.rightShiftScaleSpin.get_value())
        self.upShiftTarget = self.pango.toUnicode(self.upShiftLineEdit.get_text().strip())
        self.upShiftScale = float(self.upShiftScaleSpin.get_value())
        self.joinBeforeTarget = self.pango.toUnicode(self.joinBeforeLineEdit.get_text().strip())
        self.considerOneCharTarget = self.pango.toUnicode(self.considerOneCharLineEdit.get_text().strip())
        self.forceSmallerTarget = self.pango.toUnicode(self.forceSmallerTargetLineEdit.get_text().strip())
        self.forceSmallerScale = float(self.forceSmallerScaleSpin.get_value())
        self.rubyInfo = []
        # 配列としてアクセス
        for row in self.rubyListStore:
            rowDictionary = {}
            rowDictionary['lineNum'] = row[0]
            rowDictionary['columnNum'] = row[1]
            rowDictionary['targetText'] = self.pango.toUnicode(row[2])
            rowDictionary['rubyText'] = self.pango.toUnicode(row[3])
            rowDictionary['lineIndex'] = rowDictionary['lineNum'] - 1
            rowDictionary['columnIndex'] = rowDictionary['columnNum'] - 1
            self.rubyInfo.append(rowDictionary)
        self.savePreference()
    ########################################################################
    def run(self):
        if self.dialog:
            self.results = self.dialog.run()
            self.setResults()
            self.dialog.destroy()
            self.dialog = None
            return self.results
        else:
            return gtk.RESPONSE_CANCEL
########################################################################
########################################################################
# 縦書クラス情報取得
#
# テキストレイヤーから縦書きレイヤーを生成
#
########################################################################
class VerticalTextLayer:
    groupNameFormat = '竖排文本组 #'
    lineGroupNameFormat = '行 #'
    rubyGroupNameFormat = 'Ruby组 #'
    ########################################################################
    def __init__(self, verticalTextAccess):
        self.verticalTextAccess = verticalTextAccess
    ########################################################################
    def __del__(self):
        pass
    ########################################################################
    def create(self):
        if not self.verticalTextAccess:
            return
        if not self.verticalTextAccess.image:
            return
        if not self.verticalTextAccess.targetTextLayer:
            return
        if not self.verticalTextAccess.pango:
            return
        (fontCount, fontList) = pdb.gimp_fonts_get_list(None)
        self.verticalTextAccess.pango.setFontList(fontList)
        self.verticalTextAccess.pango.parse()
        if not self.verticalTextAccess.pango.lines:
            return
        if len(self.verticalTextAccess.pango.lines) < 1:
            return
        self.image = self.verticalTextAccess.image
        self.targetTextLayer = self.verticalTextAccess.targetTextLayer
        rubyInfo = {}
        if self.verticalTextAccess.rubyInfo and len(self.verticalTextAccess.rubyInfo) > 0:
            for info in self.verticalTextAccess.rubyInfo:
                if 'lineNum' in info.keys():
                    if info['lineNum'] in rubyInfo.keys():
                        rubyInfo[info['lineNum']].append(info)
                    else:
                        rubyInfo[info['lineNum']] = [info]
        self.useRubyFlag = self.verticalTextAccess.flags['useRuby']
        self.rubyPointSize = self.verticalTextAccess.flags['rubyPointSize']
        pdb.gimp_layer_set_visible(self.targetTextLayer, False)
        (xResolution, yResolution) = pdb.gimp_image_get_resolution(self.image)
        pointId = 1
        unitList = []
        for unitId in range(pdb.gimp_unit_get_number_of_units()):
            name = pdb.gimp_unit_get_identifier(unitId).lower()
            unitList.append(name)
            if name in ('pt', 'point', 'points'):
                pointId = unitId
        (targetLayerOffsetX, targetLayerOffsetY) = pdb.gimp_drawable_offsets(self.targetTextLayer)
        targetLayerWidth = pdb.gimp_drawable_width(self.targetTextLayer)
        targetLayerHeight = pdb.gimp_drawable_height(self.targetTextLayer)
        rightX = targetLayerOffsetX + targetLayerWidth;
        topY = targetLayerOffsetY
        defaultFont = pdb.gimp_text_layer_get_font(self.targetTextLayer)
        (defaultFontSize, defaultFontUnit) = pdb.gimp_text_layer_get_font_size(self.targetTextLayer)
        self.verticalTextAccess.pango.setDefaultFont(defaultFont)
        self.verticalTextAccess.pango.parse()
        self.lines = self.verticalTextAccess.pango.lines
        defaultColor = pdb.gimp_text_layer_get_color(self.targetTextLayer)
        antialias = pdb.gimp_text_layer_get_antialias(self.targetTextLayer)
        (hinting, autoHint) = pdb.gimp_text_layer_get_hinting(self.targetTextLayer)
        # '縦書グループ'
        index = 0
        while pdb.gimp_image_get_layer_by_name(self.image, self.groupNameFormat + str(index)):
            index += 1
        topLayerGroup = pdb.gimp_layer_group_new(self.image)
        pdb.gimp_item_set_name(topLayerGroup, self.groupNameFormat + str(index))
        position = pdb.gimp_image_get_layer_position(self.image, self.targetTextLayer)
        parent = pdb.gimp_item_get_parent(self.targetTextLayer)
        pdb.gimp_image_insert_layer(self.image, topLayerGroup, parent, position + 1)
        lineNumber = 0
        self.useRubyFlag = self.verticalTextAccess.flags['useRuby']
        self.rubyPointSize = self.verticalTextAccess.flags['rubyPointSize']
        self.rubyPixcelSize = float(self.rubyPointSize / 72.0) * xResolution
        lineSpacePoint = self.verticalTextAccess.flags['lineSpacePoint']
        lineSpacePixcel = float(lineSpacePoint / 72.0) * xResolution
        charSpacePoint = self.verticalTextAccess.flags['charSpacePoint']
        charSpacePixcel = float(charSpacePoint / 72.0) * yResolution
        ########################################################################
        for line in self.lines:
            progressVule = float(lineNumber) / float(len(self.lines))
            pdb.gimp_progress_update(progressVule)
            lineNumber += 1
            if not line:
                continue
            if len(line) < 1:
                continue
            (count, idList) = pdb.gimp_item_get_children(topLayerGroup)
            lineLayerGroup = pdb.gimp_layer_group_new(self.image)
            index = 0
            while pdb.gimp_image_get_layer_by_name(self.image,
                                                   self.lineGroupNameFormat + str(lineNumber) + ' #' + str(index)):
                index += 1
            pdb.gimp_item_set_name(lineLayerGroup, self.lineGroupNameFormat + str(lineNumber) + ' #' + str(index))
            parent = topLayerGroup
            pdb.gimp_image_insert_layer(self.image, lineLayerGroup, parent, count)
            maxWidth = 0
            for word in line:
                fontName = defaultFont
                fontSize = defaultFontSize
                unit = defaultFontUnit
                if 'point' in word.keys():
                    fontSize = float(word['point'])
                    unit = pointId
                if unit == 0:
                    if fontSize > maxWidth:
                        maxWidth = fontSize
                else:
                    width = (fontSize / pdb.gimp_unit_get_factor(unit)) * xResolution
                    if width > maxWidth:
                        maxWidth = width
            offsetY = topY
            charsIndex = 0;
            ########################################################################
            for wordIndex in range(len(line)):
                word = line[wordIndex]
                progressWordVule = float(float(wordIndex) / float(len(line))) / 10.0
                pdb.gimp_progress_update(progressVule + progressWordVule)
                fontName = defaultFont
                fontSize = defaultFontSize
                unit = defaultFontUnit
                color = defaultColor
                # width = fontSize
                if 'font' in word.keys():
                    fontName = word['font']
                if 'point' in word.keys() and self.verticalTextAccess.flags['useFontSize']:
                    fontSize = float(word['point'])
                    unit = pointId
                if 'color' in word.keys() and self.verticalTextAccess.flags['useFontColor']:
                    color = gimpcolor.RGB(float(word['color']['red']), float(word['color']['green']),
                                          float(word['color']['blue']), 1.0)
                fontPixcelSize = fontSize
                if unit == 0:
                    width = fontSize
                    fontPixcelSize = fontSize
                else:
                    width = (fontSize / pdb.gimp_unit_get_factor(unit)) * xResolution
                    fontPixcelSize = (fontSize / pdb.gimp_unit_get_factor(unit)) * xResolution
                wordTextCheck = word['text']
                if wordTextCheck.isspace():
                    wordTextCheck = 'DummyText'
                textFontExtents = pdb.gimp_text_get_extents_fontname(
                    wordTextCheck,
                    fontPixcelSize,
                    PIXELS,
                    fontName)
                textFontWidth = textFontExtents[0]
                textFontHeight = textFontExtents[1]
                textFontAscent = textFontExtents[2]
                textFontDescent = textFontExtents[3]
                charsList = []
                chaceText = u''
                ##########################################################################
                # 1文字ごとに分解
                for oneChar in word['text']:
                    if self.useRubyFlag and lineNumber in rubyInfo.keys():
                        # 行に対応するルビの準備
                        for info in rubyInfo[lineNumber]:
                            targetText = info['targetText']
                            rubyText = info['rubyText']
                            if info['columnIndex'] == charsIndex and len(targetText) > 0 and len(rubyText) > 0:
                                listIndex = len(charsList)
                                info['startIndex'] = len(charsList)
                                joinBeforeTarget = re.escape(self.verticalTextAccess.joinBeforeTarget)
                                considerOneCharTarget = self.verticalTextAccess.considerOneCharTarget
                                pattern = u'(.[' + joinBeforeTarget + '])'
                                pattern += u'|(' + considerOneCharTarget + ']+)'
                                dummyTarget = re.sub(pattern, u'X', targetText)
                                info['endIndex'] = info['startIndex'] + len(dummyTarget) - 1
                                if 'ruby' in word.keys():
                                    word['ruby'][listIndex] = info
                                else:
                                    word['ruby'] = {listIndex: info}
                    if oneChar in self.verticalTextAccess.joinBeforeTarget:
                        # 直前の文字に結合させる文字(濁点など)
                        if len(chaceText) > 0:
                            charsList.append(chaceText)
                            chaceText = u''
                        index = len(charsList) - 1
                        if index < 0:
                            charsList.append(oneChar)
                        else:
                            charsList[index] += oneChar
                    elif oneChar in self.verticalTextAccess.considerOneCharTarget:
                        # 連続しても1文字と認識させる文字(!? など)
                        chaceText += oneChar
                    else:
                        if len(chaceText) > 0:
                            charsList.append(chaceText)
                            chaceText = u''
                        charsList.append(oneChar)
                    charsIndex += 1
                if len(chaceText) > 0:
                    charsList.append(chaceText)
                    chaceText = u''
                ##########################################################################
                ruby = None
                for listIndex in range(len(charsList)):
                    progressCharVule = float(float(listIndex) / float(len(charsList))) / 100.0
                    pdb.gimp_progress_update(progressVule + progressWordVule + progressCharVule)
                    oneChar = charsList[listIndex]
                    if 'ruby' in word.keys() and listIndex in word['ruby'].keys():
                        # ルビの開始位置設定
                        ruby = word['ruby'][listIndex]
                        ruby['top'] = offsetY
                        ruby['left'] = rightX + (self.rubyPixcelSize / 2.0)
                    fontSizeRate = 1.0
                    feedSizePoint = fontSize * fontSizeRate
                    feedSizePixcel = feedSizePoint
                    if unit == 0:
                        feedSizePixcel = feedSizePoint
                    else:
                        feedSizePixcel = (feedSizePoint / pdb.gimp_unit_get_factor(unit)) * yResolution
                    if oneChar in self.verticalTextAccess.forceSmallerTarget:
                        fontSizeRate = self.verticalTextAccess.forceSmallerScale
                    if oneChar.isspace():
                        offsetY += (feedSizePixcel * fontSizeRate) + charSpacePixcel
                        continue
                    layer = pdb.gimp_text_layer_new(self.image, oneChar, fontName, fontSize * fontSizeRate, unit)
                    index = 0
                    while pdb.gimp_image_get_layer_by_name(self.image, oneChar + ' #' + str(index)):
                        index += 1
                    width = pdb.gimp_drawable_width(layer)
                    if width > maxWidth:
                        width = maxWidth
                    offsetX = rightX - maxWidth + ((maxWidth - width) / 2.0)
                    pdb.gimp_item_set_name(layer, oneChar + ' #' + str(index))
                    pdb.gimp_layer_set_offsets(layer, offsetX, offsetY)
                    (count, idList) = pdb.gimp_item_get_children(lineLayerGroup)
                    pdb.gimp_image_insert_layer(self.image, layer, lineLayerGroup, count)
                    pdb.gimp_text_layer_set_color(layer, color)
                    pdb.gimp_text_layer_set_antialias(layer, antialias)
                    pdb.gimp_text_layer_set_hinting(layer, hinting, autoHint)
                    pdb.gimp_text_layer_set_hint_style(layer, TEXT_HINT_STYLE_FULL)
                    if oneChar in self.verticalTextAccess.rotateRightTartget:
                        centerX = float(offsetX) + (pdb.gimp_drawable_width(layer) / 2.0)
                        centerY = float(offsetY) + (pdb.gimp_drawable_height(layer) / 2.0)
                        pdb.gimp_item_transform_rotate_simple(layer, ROTATE_90, False, centerX, centerY)
                    if oneChar in self.verticalTextAccess.rightShiftTarget or oneChar in self.verticalTextAccess.upShiftTarget:
                        newOffsetX = offsetX
                        newOffsetY = offsetY
                        if oneChar in self.verticalTextAccess.rightShiftTarget:
                            newOffsetX += pdb.gimp_drawable_width(layer) * self.verticalTextAccess.rightShiftScale
                        if oneChar in self.verticalTextAccess.upShiftTarget:
                            newOffsetY -= pdb.gimp_drawable_height(layer) * self.verticalTextAccess.upShiftScale
                        pdb.gimp_layer_set_offsets(layer, int(newOffsetX), int(newOffsetY))
                    ########################################################################
                    # 次の文字の開始位置
                    nextOffsetY = offsetY + (feedSizePixcel * fontSizeRate) + charSpacePixcel
                    ########################################################################
                    if ruby and ruby['endIndex'] <= listIndex and len(ruby['rubyText']) > 0:
                        # ルビの埋め込み
                        rubyGroup = pdb.gimp_layer_group_new(self.image)
                        index = 0
                        while True:
                            rubyGroupName = self.rubyGroupNameFormat + str(index)
                            if not pdb.gimp_image_get_layer_by_name(self.image, rubyGroupName):
                                break
                            index += 1
                        pdb.gimp_item_set_name(rubyGroup, rubyGroupName)
                        (count, idList) = pdb.gimp_item_get_children(lineLayerGroup)
                        pdb.gimp_image_insert_layer(self.image, rubyGroup, lineLayerGroup, count)
                        rubyFontExtents = pdb.gimp_text_get_extents_fontname(
                            ruby['rubyText'],
                            self.rubyPixcelSize,
                            PIXELS,
                            defaultFont)
                        rubyFontWidth = rubyFontExtents[0]
                        rubyFontHeight = rubyFontExtents[1]
                        rubyFontAscent = rubyFontExtents[2]
                        rubyFontDescent = rubyFontExtents[3]
                        rubyTop = ruby['top'] + math.ceil(textFontDescent * (-1) * fontSizeRate / 2.0)
                        rubyLeft = ruby['left']
                        rubyBottom = nextOffsetY - charSpacePixcel
                        rubyCharCount = len(ruby['rubyText'])
                        restRubyPixcel = (rubyBottom - rubyTop) - (rubyFontHeight * rubyCharCount)
                        rubyFeed = 0
                        if restRubyPixcel > 0:
                            rubyFeed = restRubyPixcel / (rubyCharCount + 1)
                            rubyTop += rubyFeed
                        ########################################################################
                        for rubyChar in ruby['rubyText']:
                            if rubyChar.isspace():
                                rubyTop += self.rubyPixcelSize + rubyFeed
                            rubyLayer = pdb.gimp_text_layer_new(self.image, rubyChar, defaultFont, self.rubyPointSize,
                                                                pointId)
                            index = 0
                            while True:
                                rubyName = rubyChar + ' #' + str(index)
                                if not pdb.gimp_image_get_layer_by_name(self.image, rubyName):
                                    break
                                index += 1
                            pdb.gimp_item_set_name(rubyLayer, rubyName)
                            pdb.gimp_layer_set_offsets(rubyLayer, rubyLeft, rubyTop)
                            (count, idList) = pdb.gimp_item_get_children(rubyGroup)
                            pdb.gimp_image_insert_layer(self.image, rubyLayer, rubyGroup, count)
                            pdb.gimp_text_layer_set_hinting(rubyLayer, True, True)
                            pdb.gimp_text_layer_set_hint_style(rubyLayer, TEXT_HINT_STYLE_FULL)
                            rubyTop += pdb.gimp_drawable_height(rubyLayer) + rubyFeed
                        ########################################################################
                        ruby = None
                    ########################################################################
                    # 開始位置を次の行へ
                    offsetY = nextOffsetY
            ########################################################################
            ########################################################################
            rightX -= maxWidth + lineSpacePixcel
        ########################################################################
        pdb.gimp_image_set_active_layer(self.image, topLayerGroup)
        pdb.gimp_progress_update(1.0)
########################################################################
########################################################################
# テキストレイヤーから縦書きレイヤーを作成する
#
def python_fu_vertical_writing_2_8(image, drawable):
    proc_name = 'python-fu-vertical-writing-2-8'
    verticalWriting = VerticalTextAccess(image)
    if not verticalWriting.checkUsable():
        # 使用不可
        return
    # 縦書き処理を実行
    verticalWriting.dialogMake(proc_name)
    results = verticalWriting.run()
    if results == gtk.RESPONSE_CANCEL:
        return
    layerCreator = VerticalTextLayer(verticalWriting)
    pdb.gimp_image_undo_group_start(image)
    pdb.gimp_progress_init(u'「縦書き写植」を処理中です...', None)
    try:
        layerCreator.create()
    except:
        pdb.gimp_message('処理中にエラーが発生しました。')
    pdb.gimp_progress_end()
    pdb.gimp_image_undo_group_end(image)
    return
########################################################################
register(
    'python-fu-vertical-writing-2-8',  # プロシジャの名前
    'テキストレイヤーから縦書きレイヤーを作成する。',
    # プロシジャの説明文
    '文本竖写脚本,根据文本图层创建竖排文本图层,适用于GIMP 2.8版以上。',
    # PDBに登録する追加情報
    'かんら・から',  # 作者名
    'GPLv3',  # ライセンス情報
    '2013.08.30',  # 作成日
    '文本竖书(Python)',  # メニューアイテム
    '*',  # 対応する画像タイプ
    [
        (PF_IMAGE, 'image', 'Input image', None),
        (PF_DRAWABLE, 'drawable', 'Input drawable', None)
    ],  # プロシジャの引数
    [],  # 戻り値の定義
    python_fu_vertical_writing_2_8,  # 処理を受け持つ関数名
    menu='<Image>/Layer/图层操作(Python-fu)'  # メニュー表示場所
)
main()  # プラグインを駆動させるための関数