From 34b9008242a072957a4f88d85c9d2cb730f38320 Mon Sep 17 00:00:00 2001 From: Rick Gray Date: Sun, 15 Sep 2024 14:29:16 -0500 Subject: [PATCH 1/7] initial ci/cd --- .github/workflows/release.yml | 62 +++++++++++++++++++++++++++++++++++ pyproject.toml | 30 +++++++++++++++++ src/kanjivg/__init__.py | 8 +++++ src/kanjivg/kanjivg.py | 3 ++ 4 files changed, 103 insertions(+) create mode 100644 .github/workflows/release.yml create mode 100644 pyproject.toml create mode 100644 src/kanjivg/__init__.py create mode 100644 src/kanjivg/kanjivg.py diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..b6bcbf2e3 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,62 @@ +name: Release 🚀 + +on: push + +jobs: + build: + name: Build distribution 📦 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.x" + - name: Install pypa/build + run: >- + python3 -m + pip install + build + --user + - name: Build a binary wheel and a source tarball + run: python3 -m build + - name: Store the distribution packages + uses: actions/upload-artifact@v4 + with: + name: python-package-distributions + path: dist/ + publish-to-pypi: + name: Publish to PyPI 🐍 + if: startsWith(github.ref, 'refs/tags/') + needs: + - build + runs-on: ubuntu-latest + permissions: + id-token: write + steps: + - name: Download all the dists + uses: actions/download-artifact@v4 + with: + name: python-package-distributions + path: dist/ + - name: Publish distribution 📦 to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + publish-to-testpypi: + name: Publish to TestPyPI 🐍 + needs: + - build + runs-on: ubuntu-latest + permissions: + id-token: write + steps: + - name: Download all the dists + uses: actions/download-artifact@v4 + with: + name: python-package-distributions + path: dist/ + - name: Publish distribution 📦 to TestPyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + repository-url: https://test.pypi.org/legacy/ \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 000000000..7d791c19f --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,30 @@ +[build-system] +requires = ["setuptools>=64", "setuptools_scm>=8"] +build-backend = "setuptools.build_meta" + +[project] +name = "kanjivg" +dynamic = ["version"] +authors = [ + {name = "ospalh"}, + {name = "benkasminbullock"}, + {name = "Gnurou"}, + {name = "eichhirn"} +] +description = "Kanji vector graphics " +readme = "README.md" +requires-python = ">=3.7" +license = {file = "LICENSE"} +classifiers = [ + "Programming Language :: Python :: 3", + "License :: OSI Approved", + "Operating System :: OS Independent", + "Development Status :: 5 - Production/Stable", + "Topic :: Utilities" +] + +[tool.setuptools_scm] +local_scheme = "no-local-version" + +[project.urls] +Homepage = "https://github.com/KanjiVG/kanjivg" diff --git a/src/kanjivg/__init__.py b/src/kanjivg/__init__.py new file mode 100644 index 000000000..de9a5c5c9 --- /dev/null +++ b/src/kanjivg/__init__.py @@ -0,0 +1,8 @@ +import importlib.metadata as metadata + +__version__ = metadata.version(__name__) +__author__ = metadata.metadata(__name__)['Author'] +__license__ = metadata.metadata(__name__)['License'] +__all__ = [__name__] + +from .kanjivg import * diff --git a/src/kanjivg/kanjivg.py b/src/kanjivg/kanjivg.py new file mode 100644 index 000000000..111f50f35 --- /dev/null +++ b/src/kanjivg/kanjivg.py @@ -0,0 +1,3 @@ +class kanjivg: + def __init__(self) -> None: + pass \ No newline at end of file From ba1029c06e9ef5df809c5f161884a0ce7da2161b Mon Sep 17 00:00:00 2001 From: Rick Gray Date: Sun, 15 Sep 2024 14:39:49 -0500 Subject: [PATCH 2/7] move files into source folder --- kanjivg.py | 446 -------------------- src/kanjivg/kanjivg.py | 449 ++++++++++++++++++++- kvg-lookup.py => src/kanjivg/kvg-lookup.py | 0 kvg.py => src/kanjivg/kvg.py | 0 utils.py => src/kanjivg/utils.py | 0 xmlhandler.py => src/kanjivg/xmlhandler.py | 0 6 files changed, 446 insertions(+), 449 deletions(-) delete mode 100644 kanjivg.py rename kvg-lookup.py => src/kanjivg/kvg-lookup.py (100%) mode change 100755 => 100644 rename kvg.py => src/kanjivg/kvg.py (100%) mode change 100755 => 100644 rename utils.py => src/kanjivg/utils.py (100%) rename xmlhandler.py => src/kanjivg/xmlhandler.py (100%) diff --git a/kanjivg.py b/kanjivg.py deleted file mode 100644 index 9d3099673..000000000 --- a/kanjivg.py +++ /dev/null @@ -1,446 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2009-2013 Alexandre Courbot -# -# This program 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. -# -# This program 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 . - -from xmlhandler import * -from utils import PYTHON_VERSION_MAJOR, canonicalId - -if PYTHON_VERSION_MAJOR > 2: - def unicode(s): - return s - -# Sample licence header -licenseString = """Copyright (C) 2009-2013 Ulrich Apel. -This work is distributed under the conditions of the Creative Commons -Attribution-Share Alike 3.0 Licence. This means you are free: -* to Share - to copy, distribute and transmit the work -* to Remix - to adapt the work - -Under the following conditions: -* Attribution. You must attribute the work by stating your use of KanjiVG in - your own copyright header and linking to KanjiVG's website - (http://kanjivg.tagaini.net) -* Share Alike. If you alter, transform, or build upon this work, you may - distribute the resulting work only under the same or similar license to this - one. - -See http://creativecommons.org/licenses/by-sa/3.0/ for more details.""" - -def isKanji(v): - return (v >= 0x4E00 and v <= 0x9FC3) or (v >= 0x3400 and v <= 0x4DBF) or (v >= 0xF900 and v <= 0xFAD9) or (v >= 0x2E80 and v <= 0x2EFF) or (v >= 0x20000 and v <= 0x2A6DF) - -# Returns the unicode of a character in a unicode string, taking -# surrogate pairs into account - -# Why do we need to worry about surrogate pairs? This doesn't occur in -# KanjiVG. - -def realord(s, pos = 0): - if s == None: return None - code = ord(s[pos]) - if code >= 0xD800 and code < 0xDC00: - if (len(s) <= pos + 1): - print("realord warning: missing surrogate character") - return 0 - code2 = ord(s[pos + 1]) - if code2 >= 0xDC00 and code < 0xE000: - code = 0x10000 + ((code - 0xD800) << 10) + (code2 - 0xDC00) - return code - -def realchr(i): - if i < 0x10000: return unichr(i) - else: return unichr(((i - 0x10000) >> 10) + 0xD800) + unichr(0xDC00 + (i & 0x3ff)) - -class Kanji: - """Describes a kanji. The root stroke group is accessible from the strokes member.""" - def __init__(self, code, variant = None): - # Unicode of char being represented (standard str) - self.code = canonicalId(code) - # Variant of the character, if any - self.variant = variant - self.strokes = None - - def __repr__(self): - return repr(vars(self)) - - # String identifier used to uniquely identify the kanji - def kId(self): - ret = self.code - if self.variant: ret += "-%s" % (self.variant,) - return ret - - def outputStrokesNumbers(self, out, indent = 0): - strokes = self.getStrokes() - cpt = 1 - for stroke in strokes: - stroke.numberToSVG(out, cpt, indent + 1) - cpt += 1 - - def outputStrokes(self, out, indent = 0): - self.strokes.toSVG(out, self.kId(), [0], [1]) - - def simplify(self): - self.strokes.simplify() - - def getStrokes(self): - return self.strokes.getStrokes() - - -class StrokeGr: - """Describes a stroke group belonging to a kanji as closely as possible to the XML format. Sub-stroke groups or strokes are available in the childs member. They can either be of class StrokeGr or Stroke so their type should be checked.""" - def __init__(self, parent = None): - self.parent = parent - if parent: parent.childs.append(self) - # Element of strokegr - self.element = None - # A more common, safer element this one derives of - self.original = None - self.part = None - self.number = None - self.variant = False - self.partial = False - self.tradForm = False - self.radicalForm = False - self.position = None - self.radical = None - self.phon = None - self.ID = None - - self.childs = [] - - def __repr__(self): - return repr(vars(self)) - - def setParent(self, parent): - if self.parent is not None or parent is None: - raise "Set parent should only be set once! There is no cleanup for old parents." - parent.childs.append(self) - self.parent = parent - - def toSVG(self, out, rootId, groupCpt = [0], strCpt = [1], indent = 0): - gid = rootId - if groupCpt[0] != 0: gid += "-g" + str(groupCpt[0]) - groupCpt[0] += 1 - - idString = ' id="kvg:%s"' % (gid) - eltString = "" - if self.element: eltString = ' kvg:element="%s"' % (self.element) - variantString = "" - if self.variant: variantString = ' kvg:variant="true"' - partialString = "" - if self.partial: partialString = ' kvg:partial="true"' - origString = "" - if self.original: origString = ' kvg:original="%s"' % (self.original) - partString = "" - if self.part: partString = ' kvg:part="%d"' % (self.part) - numberString = "" - if self.number: numberString = ' kvg:number="%d"' % (self.number) - tradFormString = "" - if self.tradForm: tradFormString = ' kvg:tradForm="true"' - radicalFormString = "" - if self.radicalForm: radicalFormString = ' kvg:radicalForm="true"' - posString = "" - if self.position: posString = ' kvg:position="%s"' % (self.position) - radString = "" - if self.radical: radString = ' kvg:radical="%s"' % (self.radical) - phonString = "" - if self.phon: phonString = ' kvg:phon="%s"' % (self.phon) - out.write("\t" * indent + '\n' % (idString, eltString, partString, numberString, variantString, origString, partialString, tradFormString, radicalFormString, posString, radString, phonString)) - - for child in self.childs: - child.toSVG(out, rootId, groupCpt, strCpt, indent + 1) - - out.write("\t" * indent + '\n') - - - def components(self, simplified = True, recursive = False, level = 0): - ret = [] - childsComp = [] - for child in self.childs: - if isinstance(child, StrokeGr): - found = False - # Can we find the component in the child? - if simplified and child.original: ret.append(child.original); found = True - elif child.element: ret.append(child.element); found = True - # If not, the components we are looking for are the child's - # components - we also do that if we asked all the sub-components of the group - if not found or recursive: - newLevel = level - if found: newLevel += 1 - childsComp += child.components(simplified, recursive, newLevel) - if recursive and not len(ret) == 0: ret = [ level ] + ret + childsComp - return ret - - def simplify(self): - for child in self.childs: - if isinstance(child, StrokeGr): child.simplify() - if len(self.childs) == 1 and isinstance(self.childs[0], StrokeGr): - # Check if there is no conflict - if child.element and self.element and child.element != self.element: return - if child.original and self.original and child.original != self.original: return - # Parts cannot be merged - if child.part and self.part and self.part != child.part: return - if child.variant and self.variant and child.variant != self.variant: return - if child.partial and self.partial and child.partial != self.partial: return - if child.tradForm and self.tradForm and child.tradForm != self.tradForm: return - if child.radicalForm and self.radicalForm and child.radicalForm != self.radicalForm: return - # We want to preserve inner identical positions - we may have something at the top - # of another top element, for instance. - if child.position and self.position: return - if child.radical and self.radical and child.radical != self.radical: return - if child.phon and self.phon and child.phon != self.phon: return - - # Ok, let's merge! - child = self.childs[0] - self.childs = child.childs - if child.element: self.element = child.element - if child.original: self.original = child.original - if child.part: self.part = child.part - if child.variant: self.variant = child.variant - if child.partial: self.partial = child.partial - if child.tradForm: self.tradForm = child.tradForm - if child.radicalForm: self.radicalForm = child.radicalForm - if child.position: self.position = child.position - if child.radical: self.radical = child.radical - if child.phon: self.phon = child.phon - - def getStrokes(self): - ret = [] - for child in self.childs: - if isinstance(child, StrokeGr): ret += child.getStrokes() - else: ret.append(child) - return ret - - -class Stroke: - """A single stroke, containing its type and (optionally) its SVG data.""" - def __init__(self, parent): - self.stype = None - self.svg = None - self.numberPos = None - - def __repr__(self): - return repr(vars(self)) - - def numberToSVG(self, out, number, indent = 0): - if self.numberPos: - out.write("\t" * indent + '%d\n' % (self.numberPos[0], self.numberPos[1], number)) - - def toSVG(self, out, rootId, groupCpt, strCpt, indent = 0): - pid = rootId + "-s" + str(strCpt[0]) - strCpt[0] += 1 - s = "\t" * indent + ' 1: - if (ged) not in self.compCpt: - print("%s: Numbered group %s with no first part" % (self.kanji.kId(), group.ID)) - elif self.compCpt[ged] != group.part - 1: - print("%s: Incorrectly numbered group" % (self.kanji.kId())) - # The group must not exist - else: - if (ged) in self.compCpt: - if self.compCpt[ged] == group.part: - print("%s: Duplicate group %s %s for %s part %d - %d" % (self.kanji.kId(), group.ID, ged,group.element, group.part, group.number)) - self.compCpt[ged] = group.part - # No number, just a part - groups restart with part 1, otherwise must - # increase correctly - elif group.part: - # The group must exist already - if group.part > 1: - if group.element not in self.compCpt: - print("%s: Incorrectly started multi-part group" % (self.kanji.kId())) - elif self.compCpt[group.element] != group.part - 1: - print("%s: Incorrectly split multi-part group for %s - %d" % (self.kanji.kId(),group.element,group.part)) - self.compCpt[group.element] = group.part - - def handle_end_g(self): - if self.group.parent is None: - self.groups.append(self.group) - self.group = self.group.parent - - def handle_start_path(self, attrs): - if self.kanji is None or self.group is None: - raise Exception("Stroke must be inside a kanji and group!") - stroke = Stroke(self.group) - if "kvg:type" in attrs: - stroke.stype = unicode(attrs["kvg:type"]) - if "d" in attrs: stroke.svg = unicode(attrs["d"]) - self.group.childs.append(stroke) - - - -class SVGHandler(BasicHandler): - """SVG handler for parsing final kanji files. It can handle single-kanji files or aggregation files. After parsing, the kanji are accessible through the kanjis member, indexed by their svg file name.""" - def __init__(self): - BasicHandler.__init__(self) - self.kanjis = {} - self.currentKanji = None - self.groups = [] - self.metComponents = set() - - def handle_start_g(self, attrs): - group = StrokeGr() - - # Special case for handling the root - if len(self.groups) == 0: - idType, idVariantStr = str(attrs["id"]).split("_") - idVariant = idVariantStr.split('-') - if idType == "kvg:StrokePaths": - pass - elif idType == "kvg:StrokeNumbers": - return - else: - raise Exception("Invalid root group id type (%s)" % (str(attrs["id"]),)) - self.currentKanji = Kanji(*idVariant) - self.kanjis[self.currentKanji.code] = self.currentKanji - self.compCpt = {} - else: - group.setParent(self.groups[-1]) - - # Now parse group attributes - if "kvg:element" in attrs: group.element = unicode(attrs["kvg:element"]) - if "kvg:variant" in attrs: group.variant = str(attrs["kvg:variant"]) - if "kvg:partial" in attrs: group.partial = str(attrs["kvg:partial"]) - if "kvg:original" in attrs: group.original = unicode(attrs["kvg:original"]) - if "kvg:part" in attrs: group.part = int(attrs["kvg:part"]) - if "kvg:number" in attrs: group.number = int(attrs["kvg:number"]) - if "kvg:tradForm" in attrs and str(attrs["kvg:tradForm"]) == "true": group.tradForm = True - if "kvg:radicalForm" in attrs and str(attrs["kvg:radicalForm"]) == "true": group.radicalForm = True - if "kvg:position" in attrs: group.position = unicode(attrs["kvg:position"]) - if "kvg:radical" in attrs: group.radical = unicode(attrs["kvg:radical"]) - if "kvg:phon" in attrs: group.phon = unicode(attrs["kvg:phon"]) - - self.groups.append(group) - - if group.element: self.metComponents.add(group.element) - if group.original: self.metComponents.add(group.original) - - # This code seems to be duplicated in the XML and SVG code and - # possibly should be unified. - if group.number: - if not group.part: - print("%s: Number specified, but part missing" % (self.currentKanji.kId())) - ged = group.element + "n" + str(group.number) - if group.part > 1: - if (ged) not in self.compCpt: - print("%s: Missing numbered group" % (self.currentKanji.kId())) - elif self.compCpt[ged] != group.part - 1: - print("%s: Incorrectly numbered group" % (self.currentKanji.kId())) - # The group must not exist - else: - if (ged) not in self.compCpt: - print("%s: Duplicate numbered group %d" % (self.currentKanji.kId(), group.number)) - self.compCpt[ged] = group.part - # No number, just a part - groups restart with part 1, otherwise must - # increase correctly - elif group.part: - # The group must exist already - if group.part > 1: - if (group.element) not in self.compCpt: - print("%s: Incorrectly started multi-part group" % (self.currentKanji.kId())) - elif self.compCpt[group.element] != group.part - 1: - print("%s: Incorrectly splitted multi-part group" % (self.currentKanji.kId())) - self.compCpt[group.element] = group.part - - def handle_end_g(self): - if len(self.groups) == 0: - return - group = self.groups.pop() - # End of kanji? - if len(self.groups) == 1: # index 1 - ignore root group - self.currentKanji.strokes = group - self.currentKanji = None - self.groups = [] - - - def handle_start_path(self, attrs): - if len(self.groups) == 0: parent = None - else: parent = self.groups[-1] - stroke = Stroke(parent) - if "kvg:type" in attrs: - stroke.stype = unicode(attrs["kvg:type"]) - if "d" in attrs: - stroke.svg = unicode(attrs["d"]) - self.groups[-1].childs.append(stroke) diff --git a/src/kanjivg/kanjivg.py b/src/kanjivg/kanjivg.py index 111f50f35..9d3099673 100644 --- a/src/kanjivg/kanjivg.py +++ b/src/kanjivg/kanjivg.py @@ -1,3 +1,446 @@ -class kanjivg: - def __init__(self) -> None: - pass \ No newline at end of file +# -*- coding: utf-8 -*- +# +# Copyright (C) 2009-2013 Alexandre Courbot +# +# This program 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. +# +# This program 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 . + +from xmlhandler import * +from utils import PYTHON_VERSION_MAJOR, canonicalId + +if PYTHON_VERSION_MAJOR > 2: + def unicode(s): + return s + +# Sample licence header +licenseString = """Copyright (C) 2009-2013 Ulrich Apel. +This work is distributed under the conditions of the Creative Commons +Attribution-Share Alike 3.0 Licence. This means you are free: +* to Share - to copy, distribute and transmit the work +* to Remix - to adapt the work + +Under the following conditions: +* Attribution. You must attribute the work by stating your use of KanjiVG in + your own copyright header and linking to KanjiVG's website + (http://kanjivg.tagaini.net) +* Share Alike. If you alter, transform, or build upon this work, you may + distribute the resulting work only under the same or similar license to this + one. + +See http://creativecommons.org/licenses/by-sa/3.0/ for more details.""" + +def isKanji(v): + return (v >= 0x4E00 and v <= 0x9FC3) or (v >= 0x3400 and v <= 0x4DBF) or (v >= 0xF900 and v <= 0xFAD9) or (v >= 0x2E80 and v <= 0x2EFF) or (v >= 0x20000 and v <= 0x2A6DF) + +# Returns the unicode of a character in a unicode string, taking +# surrogate pairs into account + +# Why do we need to worry about surrogate pairs? This doesn't occur in +# KanjiVG. + +def realord(s, pos = 0): + if s == None: return None + code = ord(s[pos]) + if code >= 0xD800 and code < 0xDC00: + if (len(s) <= pos + 1): + print("realord warning: missing surrogate character") + return 0 + code2 = ord(s[pos + 1]) + if code2 >= 0xDC00 and code < 0xE000: + code = 0x10000 + ((code - 0xD800) << 10) + (code2 - 0xDC00) + return code + +def realchr(i): + if i < 0x10000: return unichr(i) + else: return unichr(((i - 0x10000) >> 10) + 0xD800) + unichr(0xDC00 + (i & 0x3ff)) + +class Kanji: + """Describes a kanji. The root stroke group is accessible from the strokes member.""" + def __init__(self, code, variant = None): + # Unicode of char being represented (standard str) + self.code = canonicalId(code) + # Variant of the character, if any + self.variant = variant + self.strokes = None + + def __repr__(self): + return repr(vars(self)) + + # String identifier used to uniquely identify the kanji + def kId(self): + ret = self.code + if self.variant: ret += "-%s" % (self.variant,) + return ret + + def outputStrokesNumbers(self, out, indent = 0): + strokes = self.getStrokes() + cpt = 1 + for stroke in strokes: + stroke.numberToSVG(out, cpt, indent + 1) + cpt += 1 + + def outputStrokes(self, out, indent = 0): + self.strokes.toSVG(out, self.kId(), [0], [1]) + + def simplify(self): + self.strokes.simplify() + + def getStrokes(self): + return self.strokes.getStrokes() + + +class StrokeGr: + """Describes a stroke group belonging to a kanji as closely as possible to the XML format. Sub-stroke groups or strokes are available in the childs member. They can either be of class StrokeGr or Stroke so their type should be checked.""" + def __init__(self, parent = None): + self.parent = parent + if parent: parent.childs.append(self) + # Element of strokegr + self.element = None + # A more common, safer element this one derives of + self.original = None + self.part = None + self.number = None + self.variant = False + self.partial = False + self.tradForm = False + self.radicalForm = False + self.position = None + self.radical = None + self.phon = None + self.ID = None + + self.childs = [] + + def __repr__(self): + return repr(vars(self)) + + def setParent(self, parent): + if self.parent is not None or parent is None: + raise "Set parent should only be set once! There is no cleanup for old parents." + parent.childs.append(self) + self.parent = parent + + def toSVG(self, out, rootId, groupCpt = [0], strCpt = [1], indent = 0): + gid = rootId + if groupCpt[0] != 0: gid += "-g" + str(groupCpt[0]) + groupCpt[0] += 1 + + idString = ' id="kvg:%s"' % (gid) + eltString = "" + if self.element: eltString = ' kvg:element="%s"' % (self.element) + variantString = "" + if self.variant: variantString = ' kvg:variant="true"' + partialString = "" + if self.partial: partialString = ' kvg:partial="true"' + origString = "" + if self.original: origString = ' kvg:original="%s"' % (self.original) + partString = "" + if self.part: partString = ' kvg:part="%d"' % (self.part) + numberString = "" + if self.number: numberString = ' kvg:number="%d"' % (self.number) + tradFormString = "" + if self.tradForm: tradFormString = ' kvg:tradForm="true"' + radicalFormString = "" + if self.radicalForm: radicalFormString = ' kvg:radicalForm="true"' + posString = "" + if self.position: posString = ' kvg:position="%s"' % (self.position) + radString = "" + if self.radical: radString = ' kvg:radical="%s"' % (self.radical) + phonString = "" + if self.phon: phonString = ' kvg:phon="%s"' % (self.phon) + out.write("\t" * indent + '\n' % (idString, eltString, partString, numberString, variantString, origString, partialString, tradFormString, radicalFormString, posString, radString, phonString)) + + for child in self.childs: + child.toSVG(out, rootId, groupCpt, strCpt, indent + 1) + + out.write("\t" * indent + '\n') + + + def components(self, simplified = True, recursive = False, level = 0): + ret = [] + childsComp = [] + for child in self.childs: + if isinstance(child, StrokeGr): + found = False + # Can we find the component in the child? + if simplified and child.original: ret.append(child.original); found = True + elif child.element: ret.append(child.element); found = True + # If not, the components we are looking for are the child's + # components - we also do that if we asked all the sub-components of the group + if not found or recursive: + newLevel = level + if found: newLevel += 1 + childsComp += child.components(simplified, recursive, newLevel) + if recursive and not len(ret) == 0: ret = [ level ] + ret + childsComp + return ret + + def simplify(self): + for child in self.childs: + if isinstance(child, StrokeGr): child.simplify() + if len(self.childs) == 1 and isinstance(self.childs[0], StrokeGr): + # Check if there is no conflict + if child.element and self.element and child.element != self.element: return + if child.original and self.original and child.original != self.original: return + # Parts cannot be merged + if child.part and self.part and self.part != child.part: return + if child.variant and self.variant and child.variant != self.variant: return + if child.partial and self.partial and child.partial != self.partial: return + if child.tradForm and self.tradForm and child.tradForm != self.tradForm: return + if child.radicalForm and self.radicalForm and child.radicalForm != self.radicalForm: return + # We want to preserve inner identical positions - we may have something at the top + # of another top element, for instance. + if child.position and self.position: return + if child.radical and self.radical and child.radical != self.radical: return + if child.phon and self.phon and child.phon != self.phon: return + + # Ok, let's merge! + child = self.childs[0] + self.childs = child.childs + if child.element: self.element = child.element + if child.original: self.original = child.original + if child.part: self.part = child.part + if child.variant: self.variant = child.variant + if child.partial: self.partial = child.partial + if child.tradForm: self.tradForm = child.tradForm + if child.radicalForm: self.radicalForm = child.radicalForm + if child.position: self.position = child.position + if child.radical: self.radical = child.radical + if child.phon: self.phon = child.phon + + def getStrokes(self): + ret = [] + for child in self.childs: + if isinstance(child, StrokeGr): ret += child.getStrokes() + else: ret.append(child) + return ret + + +class Stroke: + """A single stroke, containing its type and (optionally) its SVG data.""" + def __init__(self, parent): + self.stype = None + self.svg = None + self.numberPos = None + + def __repr__(self): + return repr(vars(self)) + + def numberToSVG(self, out, number, indent = 0): + if self.numberPos: + out.write("\t" * indent + '%d\n' % (self.numberPos[0], self.numberPos[1], number)) + + def toSVG(self, out, rootId, groupCpt, strCpt, indent = 0): + pid = rootId + "-s" + str(strCpt[0]) + strCpt[0] += 1 + s = "\t" * indent + ' 1: + if (ged) not in self.compCpt: + print("%s: Numbered group %s with no first part" % (self.kanji.kId(), group.ID)) + elif self.compCpt[ged] != group.part - 1: + print("%s: Incorrectly numbered group" % (self.kanji.kId())) + # The group must not exist + else: + if (ged) in self.compCpt: + if self.compCpt[ged] == group.part: + print("%s: Duplicate group %s %s for %s part %d - %d" % (self.kanji.kId(), group.ID, ged,group.element, group.part, group.number)) + self.compCpt[ged] = group.part + # No number, just a part - groups restart with part 1, otherwise must + # increase correctly + elif group.part: + # The group must exist already + if group.part > 1: + if group.element not in self.compCpt: + print("%s: Incorrectly started multi-part group" % (self.kanji.kId())) + elif self.compCpt[group.element] != group.part - 1: + print("%s: Incorrectly split multi-part group for %s - %d" % (self.kanji.kId(),group.element,group.part)) + self.compCpt[group.element] = group.part + + def handle_end_g(self): + if self.group.parent is None: + self.groups.append(self.group) + self.group = self.group.parent + + def handle_start_path(self, attrs): + if self.kanji is None or self.group is None: + raise Exception("Stroke must be inside a kanji and group!") + stroke = Stroke(self.group) + if "kvg:type" in attrs: + stroke.stype = unicode(attrs["kvg:type"]) + if "d" in attrs: stroke.svg = unicode(attrs["d"]) + self.group.childs.append(stroke) + + + +class SVGHandler(BasicHandler): + """SVG handler for parsing final kanji files. It can handle single-kanji files or aggregation files. After parsing, the kanji are accessible through the kanjis member, indexed by their svg file name.""" + def __init__(self): + BasicHandler.__init__(self) + self.kanjis = {} + self.currentKanji = None + self.groups = [] + self.metComponents = set() + + def handle_start_g(self, attrs): + group = StrokeGr() + + # Special case for handling the root + if len(self.groups) == 0: + idType, idVariantStr = str(attrs["id"]).split("_") + idVariant = idVariantStr.split('-') + if idType == "kvg:StrokePaths": + pass + elif idType == "kvg:StrokeNumbers": + return + else: + raise Exception("Invalid root group id type (%s)" % (str(attrs["id"]),)) + self.currentKanji = Kanji(*idVariant) + self.kanjis[self.currentKanji.code] = self.currentKanji + self.compCpt = {} + else: + group.setParent(self.groups[-1]) + + # Now parse group attributes + if "kvg:element" in attrs: group.element = unicode(attrs["kvg:element"]) + if "kvg:variant" in attrs: group.variant = str(attrs["kvg:variant"]) + if "kvg:partial" in attrs: group.partial = str(attrs["kvg:partial"]) + if "kvg:original" in attrs: group.original = unicode(attrs["kvg:original"]) + if "kvg:part" in attrs: group.part = int(attrs["kvg:part"]) + if "kvg:number" in attrs: group.number = int(attrs["kvg:number"]) + if "kvg:tradForm" in attrs and str(attrs["kvg:tradForm"]) == "true": group.tradForm = True + if "kvg:radicalForm" in attrs and str(attrs["kvg:radicalForm"]) == "true": group.radicalForm = True + if "kvg:position" in attrs: group.position = unicode(attrs["kvg:position"]) + if "kvg:radical" in attrs: group.radical = unicode(attrs["kvg:radical"]) + if "kvg:phon" in attrs: group.phon = unicode(attrs["kvg:phon"]) + + self.groups.append(group) + + if group.element: self.metComponents.add(group.element) + if group.original: self.metComponents.add(group.original) + + # This code seems to be duplicated in the XML and SVG code and + # possibly should be unified. + if group.number: + if not group.part: + print("%s: Number specified, but part missing" % (self.currentKanji.kId())) + ged = group.element + "n" + str(group.number) + if group.part > 1: + if (ged) not in self.compCpt: + print("%s: Missing numbered group" % (self.currentKanji.kId())) + elif self.compCpt[ged] != group.part - 1: + print("%s: Incorrectly numbered group" % (self.currentKanji.kId())) + # The group must not exist + else: + if (ged) not in self.compCpt: + print("%s: Duplicate numbered group %d" % (self.currentKanji.kId(), group.number)) + self.compCpt[ged] = group.part + # No number, just a part - groups restart with part 1, otherwise must + # increase correctly + elif group.part: + # The group must exist already + if group.part > 1: + if (group.element) not in self.compCpt: + print("%s: Incorrectly started multi-part group" % (self.currentKanji.kId())) + elif self.compCpt[group.element] != group.part - 1: + print("%s: Incorrectly splitted multi-part group" % (self.currentKanji.kId())) + self.compCpt[group.element] = group.part + + def handle_end_g(self): + if len(self.groups) == 0: + return + group = self.groups.pop() + # End of kanji? + if len(self.groups) == 1: # index 1 - ignore root group + self.currentKanji.strokes = group + self.currentKanji = None + self.groups = [] + + + def handle_start_path(self, attrs): + if len(self.groups) == 0: parent = None + else: parent = self.groups[-1] + stroke = Stroke(parent) + if "kvg:type" in attrs: + stroke.stype = unicode(attrs["kvg:type"]) + if "d" in attrs: + stroke.svg = unicode(attrs["d"]) + self.groups[-1].childs.append(stroke) diff --git a/kvg-lookup.py b/src/kanjivg/kvg-lookup.py old mode 100755 new mode 100644 similarity index 100% rename from kvg-lookup.py rename to src/kanjivg/kvg-lookup.py diff --git a/kvg.py b/src/kanjivg/kvg.py old mode 100755 new mode 100644 similarity index 100% rename from kvg.py rename to src/kanjivg/kvg.py diff --git a/utils.py b/src/kanjivg/utils.py similarity index 100% rename from utils.py rename to src/kanjivg/utils.py diff --git a/xmlhandler.py b/src/kanjivg/xmlhandler.py similarity index 100% rename from xmlhandler.py rename to src/kanjivg/xmlhandler.py From e98e5659e89de4ee562355f2f3b65434fcda7ca4 Mon Sep 17 00:00:00 2001 From: Rick Gray Date: Sun, 15 Sep 2024 14:50:05 -0500 Subject: [PATCH 3/7] import scripts to init --- src/kanjivg/__init__.py | 2 ++ src/kanjivg/{kvg-lookup.py => kvg_lookup.py} | 0 2 files changed, 2 insertions(+) rename src/kanjivg/{kvg-lookup.py => kvg_lookup.py} (100%) diff --git a/src/kanjivg/__init__.py b/src/kanjivg/__init__.py index de9a5c5c9..7ef4540a8 100644 --- a/src/kanjivg/__init__.py +++ b/src/kanjivg/__init__.py @@ -6,3 +6,5 @@ __license__ = metadata.metadata(__name__)['License'] __all__ = [__name__] from .kanjivg import * +from .kvg_lookup import * +from .kvg import * \ No newline at end of file diff --git a/src/kanjivg/kvg-lookup.py b/src/kanjivg/kvg_lookup.py similarity index 100% rename from src/kanjivg/kvg-lookup.py rename to src/kanjivg/kvg_lookup.py From 540924e20624c9da6909425a34cab2892a0cc903 Mon Sep 17 00:00:00 2001 From: Rick Gray Date: Sun, 15 Sep 2024 15:02:56 -0500 Subject: [PATCH 4/7] fix imports --- .gitignore | 1 + src/kanjivg/kanjivg.py | 4 ++-- src/kanjivg/kvg.py | 4 ++-- src/kanjivg/kvg_lookup.py | 4 ++-- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index e22c10752..61125123f 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ kanjivg.xml kanjivg-*xml.gz kanjivg-*.zip stripped/* +*.egg-info \ No newline at end of file diff --git a/src/kanjivg/kanjivg.py b/src/kanjivg/kanjivg.py index 9d3099673..c9b50b6da 100644 --- a/src/kanjivg/kanjivg.py +++ b/src/kanjivg/kanjivg.py @@ -15,8 +15,8 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -from xmlhandler import * -from utils import PYTHON_VERSION_MAJOR, canonicalId +from .xmlhandler import * +from .utils import PYTHON_VERSION_MAJOR, canonicalId if PYTHON_VERSION_MAJOR > 2: def unicode(s): diff --git a/src/kanjivg/kvg.py b/src/kanjivg/kvg.py index a54b3e544..8624f1f4c 100644 --- a/src/kanjivg/kvg.py +++ b/src/kanjivg/kvg.py @@ -17,8 +17,8 @@ # along with this program. If not, see . import sys, os, re, datetime -from kanjivg import licenseString -from utils import open +from .kanjivg import licenseString +from .utils import open verbose = False diff --git a/src/kanjivg/kvg_lookup.py b/src/kanjivg/kvg_lookup.py index f21d0bbd8..b11c380ad 100644 --- a/src/kanjivg/kvg_lookup.py +++ b/src/kanjivg/kvg_lookup.py @@ -18,8 +18,8 @@ # along with this program. If not, see . import sys, os, re, datetime -from kanjivg import Stroke, StrokeGr -from utils import listSvgFiles, readXmlFile, canonicalId, PYTHON_VERSION_MAJOR +from .kanjivg import Stroke, StrokeGr +from .utils import listSvgFiles, readXmlFile, canonicalId, PYTHON_VERSION_MAJOR if PYTHON_VERSION_MAJOR > 2: def unicode(s): From 6758a0d1aa75357ec01c99f342cfc541bc65cbe4 Mon Sep 17 00:00:00 2001 From: Rick Gray Date: Sun, 15 Sep 2024 15:08:26 -0500 Subject: [PATCH 5/7] move source to original location --- src/kanjivg/__init__.py => __init__.py | 0 src/kanjivg/kanjivg.py => kanjivg.py | 0 src/kanjivg/kvg.py => kvg.py | 0 src/kanjivg/kvg_lookup.py => kvg_lookup.py | 0 src/kanjivg/utils.py => utils.py | 0 src/kanjivg/xmlhandler.py => xmlhandler.py | 0 6 files changed, 0 insertions(+), 0 deletions(-) rename src/kanjivg/__init__.py => __init__.py (100%) rename src/kanjivg/kanjivg.py => kanjivg.py (100%) rename src/kanjivg/kvg.py => kvg.py (100%) rename src/kanjivg/kvg_lookup.py => kvg_lookup.py (100%) rename src/kanjivg/utils.py => utils.py (100%) rename src/kanjivg/xmlhandler.py => xmlhandler.py (100%) diff --git a/src/kanjivg/__init__.py b/__init__.py similarity index 100% rename from src/kanjivg/__init__.py rename to __init__.py diff --git a/src/kanjivg/kanjivg.py b/kanjivg.py similarity index 100% rename from src/kanjivg/kanjivg.py rename to kanjivg.py diff --git a/src/kanjivg/kvg.py b/kvg.py similarity index 100% rename from src/kanjivg/kvg.py rename to kvg.py diff --git a/src/kanjivg/kvg_lookup.py b/kvg_lookup.py similarity index 100% rename from src/kanjivg/kvg_lookup.py rename to kvg_lookup.py diff --git a/src/kanjivg/utils.py b/utils.py similarity index 100% rename from src/kanjivg/utils.py rename to utils.py diff --git a/src/kanjivg/xmlhandler.py b/xmlhandler.py similarity index 100% rename from src/kanjivg/xmlhandler.py rename to xmlhandler.py From 32fbdb5c5da03c7cd0fe922fdbe31a4d3839bd57 Mon Sep 17 00:00:00 2001 From: Rick Gray Date: Sun, 15 Sep 2024 15:08:42 -0500 Subject: [PATCH 6/7] fix relative imports --- __init__.py | 6 +++--- kanjivg.py | 4 ++-- kvg.py | 4 ++-- kvg_lookup.py | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/__init__.py b/__init__.py index 7ef4540a8..8599030eb 100644 --- a/__init__.py +++ b/__init__.py @@ -5,6 +5,6 @@ __author__ = metadata.metadata(__name__)['Author'] __license__ = metadata.metadata(__name__)['License'] __all__ = [__name__] -from .kanjivg import * -from .kvg_lookup import * -from .kvg import * \ No newline at end of file +from kanjivg import * +from kvg_lookup import * +from kvg import * \ No newline at end of file diff --git a/kanjivg.py b/kanjivg.py index c9b50b6da..9d3099673 100644 --- a/kanjivg.py +++ b/kanjivg.py @@ -15,8 +15,8 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -from .xmlhandler import * -from .utils import PYTHON_VERSION_MAJOR, canonicalId +from xmlhandler import * +from utils import PYTHON_VERSION_MAJOR, canonicalId if PYTHON_VERSION_MAJOR > 2: def unicode(s): diff --git a/kvg.py b/kvg.py index 8624f1f4c..a54b3e544 100644 --- a/kvg.py +++ b/kvg.py @@ -17,8 +17,8 @@ # along with this program. If not, see . import sys, os, re, datetime -from .kanjivg import licenseString -from .utils import open +from kanjivg import licenseString +from utils import open verbose = False diff --git a/kvg_lookup.py b/kvg_lookup.py index b11c380ad..f21d0bbd8 100644 --- a/kvg_lookup.py +++ b/kvg_lookup.py @@ -18,8 +18,8 @@ # along with this program. If not, see . import sys, os, re, datetime -from .kanjivg import Stroke, StrokeGr -from .utils import listSvgFiles, readXmlFile, canonicalId, PYTHON_VERSION_MAJOR +from kanjivg import Stroke, StrokeGr +from utils import listSvgFiles, readXmlFile, canonicalId, PYTHON_VERSION_MAJOR if PYTHON_VERSION_MAJOR > 2: def unicode(s): From f3c28b316ae8273a1474a02c3e6f0ba2adcd0c2f Mon Sep 17 00:00:00 2001 From: Rick Gray Date: Fri, 20 Sep 2024 21:52:56 -0500 Subject: [PATCH 7/7] handle dated versioning --- pyproject.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 7d791c19f..4db3f5a75 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,7 +24,9 @@ classifiers = [ ] [tool.setuptools_scm] +version_scheme = "no-guess-dev" local_scheme = "no-local-version" +tag_regex = "^(?:r)?(\\d{8}-?\\d?)$" [project.urls] Homepage = "https://github.com/KanjiVG/kanjivg"