diff --git a/tools/kothic/drules_struct_pb2.py b/tools/kothic/drules_struct_pb2.py
new file mode 100644
index 0000000000..47d1db5510
--- /dev/null
+++ b/tools/kothic/drules_struct_pb2.py
@@ -0,0 +1,817 @@
+# Generated by the protocol buffer compiler. DO NOT EDIT!
+
+from google.protobuf import descriptor
+from google.protobuf import message
+from google.protobuf import reflection
+from google.protobuf import descriptor_pb2
+# @@protoc_insertion_point(imports)
+
+
+DESCRIPTOR = descriptor.FileDescriptor(
+ name='drules_struct.proto',
+ package='',
+ serialized_pb='\n\x13\x64rules_struct.proto\"*\n\x0c\x44\x61shDotProto\x12\n\n\x02\x64\x64\x18\x01 \x03(\x01\x12\x0e\n\x06offset\x18\x02 \x01(\x01\":\n\x0cPathSymProto\x12\x0c\n\x04name\x18\x01 \x02(\t\x12\x0c\n\x04step\x18\x02 \x02(\x01\x12\x0e\n\x06offset\x18\x03 \x01(\x01\"\xaf\x01\n\rLineRuleProto\x12\r\n\x05width\x18\x01 \x02(\x01\x12\r\n\x05\x63olor\x18\x02 \x02(\r\x12\x1e\n\x07\x64\x61shdot\x18\x03 \x01(\x0b\x32\r.DashDotProto\x12\x10\n\x08priority\x18\x04 \x02(\x05\x12\x1e\n\x07pathsym\x18\x05 \x01(\x0b\x32\r.PathSymProto\x12\x17\n\x04join\x18\x06 \x01(\x0e\x32\t.LineJoin\x12\x15\n\x03\x63\x61p\x18\x07 \x01(\x0e\x32\x08.LineCap\"\x9c\x01\n\x0cLineDefProto\x12\r\n\x05width\x18\x01 \x02(\x01\x12\r\n\x05\x63olor\x18\x02 \x02(\r\x12\x1e\n\x07\x64\x61shdot\x18\x03 \x01(\x0b\x32\r.DashDotProto\x12\x1e\n\x07pathsym\x18\x04 \x01(\x0b\x32\r.PathSymProto\x12\x17\n\x04join\x18\x06 \x01(\x0e\x32\t.LineJoin\x12\x15\n\x03\x63\x61p\x18\x07 \x01(\x0e\x32\x08.LineCap\"O\n\rAreaRuleProto\x12\r\n\x05\x63olor\x18\x01 \x02(\r\x12\x1d\n\x06\x62order\x18\x02 \x01(\x0b\x32\r.LineDefProto\x12\x10\n\x08priority\x18\x03 \x02(\x05\"I\n\x0fSymbolRuleProto\x12\x0c\n\x04name\x18\x01 \x02(\t\x12\x16\n\x0e\x61pply_for_type\x18\x02 \x01(\x05\x12\x10\n\x08priority\x18\x03 \x02(\x05\"j\n\x0f\x43\x61ptionDefProto\x12\x0e\n\x06height\x18\x01 \x02(\x05\x12\r\n\x05\x63olor\x18\x02 \x02(\r\x12\x14\n\x0cstroke_color\x18\x03 \x01(\r\x12\x10\n\x08offset_x\x18\x04 \x01(\x05\x12\x10\n\x08offset_y\x18\x05 \x01(\x05\"l\n\x10\x43\x61ptionRuleProto\x12!\n\x07primary\x18\x01 \x02(\x0b\x32\x10.CaptionDefProto\x12#\n\tsecondary\x18\x02 \x01(\x0b\x32\x10.CaptionDefProto\x12\x10\n\x08priority\x18\x03 \x02(\x05\"a\n\x0f\x43ircleRuleProto\x12\x0e\n\x06radius\x18\x01 \x02(\x01\x12\r\n\x05\x63olor\x18\x02 \x02(\r\x12\x1d\n\x06\x62order\x18\x03 \x01(\x0b\x32\r.LineDefProto\x12\x10\n\x08priority\x18\x04 \x02(\x05\"m\n\x11PathTextRuleProto\x12!\n\x07primary\x18\x01 \x02(\x0b\x32\x10.CaptionDefProto\x12#\n\tsecondary\x18\x02 \x01(\x0b\x32\x10.CaptionDefProto\x12\x10\n\x08priority\x18\x03 \x02(\x05\"\xed\x01\n\x10\x44rawElementProto\x12\r\n\x05scale\x18\x01 \x02(\x05\x12\x1d\n\x05lines\x18\x02 \x03(\x0b\x32\x0e.LineRuleProto\x12\x1c\n\x04\x61rea\x18\x03 \x01(\x0b\x32\x0e.AreaRuleProto\x12 \n\x06symbol\x18\x04 \x01(\x0b\x32\x10.SymbolRuleProto\x12\"\n\x07\x63\x61ption\x18\x05 \x01(\x0b\x32\x11.CaptionRuleProto\x12 \n\x06\x63ircle\x18\x06 \x01(\x0b\x32\x10.CircleRuleProto\x12%\n\tpath_text\x18\x07 \x01(\x0b\x32\x12.PathTextRuleProto\"G\n\x13\x43lassifElementProto\x12\x0c\n\x04name\x18\x01 \x02(\t\x12\"\n\x07\x65lement\x18\x02 \x03(\x0b\x32\x11.DrawElementProto\"4\n\x0e\x43ontainerProto\x12\"\n\x04\x63ont\x18\x01 \x03(\x0b\x32\x14.ClassifElementProto*4\n\x08LineJoin\x12\r\n\tROUNDJOIN\x10\x00\x12\r\n\tBEVELJOIN\x10\x01\x12\n\n\x06NOJOIN\x10\x02*3\n\x07LineCap\x12\x0c\n\x08ROUNDCAP\x10\x00\x12\x0b\n\x07\x42UTTCAP\x10\x01\x12\r\n\tSQUARECAP\x10\x02\x42\x02H\x03')
+
+_LINEJOIN = descriptor.EnumDescriptor(
+ name='LineJoin',
+ full_name='LineJoin',
+ filename=None,
+ file=DESCRIPTOR,
+ values=[
+ descriptor.EnumValueDescriptor(
+ name='ROUNDJOIN', index=0, number=0,
+ options=None,
+ type=None),
+ descriptor.EnumValueDescriptor(
+ name='BEVELJOIN', index=1, number=1,
+ options=None,
+ type=None),
+ descriptor.EnumValueDescriptor(
+ name='NOJOIN', index=2, number=2,
+ options=None,
+ type=None),
+ ],
+ containing_type=None,
+ options=None,
+ serialized_start=1415,
+ serialized_end=1467,
+)
+
+
+_LINECAP = descriptor.EnumDescriptor(
+ name='LineCap',
+ full_name='LineCap',
+ filename=None,
+ file=DESCRIPTOR,
+ values=[
+ descriptor.EnumValueDescriptor(
+ name='ROUNDCAP', index=0, number=0,
+ options=None,
+ type=None),
+ descriptor.EnumValueDescriptor(
+ name='BUTTCAP', index=1, number=1,
+ options=None,
+ type=None),
+ descriptor.EnumValueDescriptor(
+ name='SQUARECAP', index=2, number=2,
+ options=None,
+ type=None),
+ ],
+ containing_type=None,
+ options=None,
+ serialized_start=1469,
+ serialized_end=1520,
+)
+
+
+ROUNDJOIN = 0
+BEVELJOIN = 1
+NOJOIN = 2
+ROUNDCAP = 0
+BUTTCAP = 1
+SQUARECAP = 2
+
+
+_DASHDOTPROTO = descriptor.Descriptor(
+ name='DashDotProto',
+ full_name='DashDotProto',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ descriptor.FieldDescriptor(
+ name='dd', full_name='DashDotProto.dd', index=0,
+ number=1, type=1, cpp_type=5, label=3,
+ has_default_value=False, default_value=[],
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ descriptor.FieldDescriptor(
+ name='offset', full_name='DashDotProto.offset', index=1,
+ number=2, type=1, cpp_type=5, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ options=None,
+ is_extendable=False,
+ extension_ranges=[],
+ serialized_start=23,
+ serialized_end=65,
+)
+
+
+_PATHSYMPROTO = descriptor.Descriptor(
+ name='PathSymProto',
+ full_name='PathSymProto',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ descriptor.FieldDescriptor(
+ name='name', full_name='PathSymProto.name', index=0,
+ number=1, type=9, cpp_type=9, label=2,
+ has_default_value=False, default_value=unicode("", "utf-8"),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ descriptor.FieldDescriptor(
+ name='step', full_name='PathSymProto.step', index=1,
+ number=2, type=1, cpp_type=5, label=2,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ descriptor.FieldDescriptor(
+ name='offset', full_name='PathSymProto.offset', index=2,
+ number=3, type=1, cpp_type=5, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ options=None,
+ is_extendable=False,
+ extension_ranges=[],
+ serialized_start=67,
+ serialized_end=125,
+)
+
+
+_LINERULEPROTO = descriptor.Descriptor(
+ name='LineRuleProto',
+ full_name='LineRuleProto',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ descriptor.FieldDescriptor(
+ name='width', full_name='LineRuleProto.width', index=0,
+ number=1, type=1, cpp_type=5, label=2,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ descriptor.FieldDescriptor(
+ name='color', full_name='LineRuleProto.color', index=1,
+ number=2, type=13, cpp_type=3, label=2,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ descriptor.FieldDescriptor(
+ name='dashdot', full_name='LineRuleProto.dashdot', index=2,
+ number=3, type=11, cpp_type=10, label=1,
+ has_default_value=False, default_value=None,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ descriptor.FieldDescriptor(
+ name='priority', full_name='LineRuleProto.priority', index=3,
+ number=4, type=5, cpp_type=1, label=2,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ descriptor.FieldDescriptor(
+ name='pathsym', full_name='LineRuleProto.pathsym', index=4,
+ number=5, type=11, cpp_type=10, label=1,
+ has_default_value=False, default_value=None,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ descriptor.FieldDescriptor(
+ name='join', full_name='LineRuleProto.join', index=5,
+ number=6, type=14, cpp_type=8, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ descriptor.FieldDescriptor(
+ name='cap', full_name='LineRuleProto.cap', index=6,
+ number=7, type=14, cpp_type=8, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ options=None,
+ is_extendable=False,
+ extension_ranges=[],
+ serialized_start=128,
+ serialized_end=303,
+)
+
+
+_LINEDEFPROTO = descriptor.Descriptor(
+ name='LineDefProto',
+ full_name='LineDefProto',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ descriptor.FieldDescriptor(
+ name='width', full_name='LineDefProto.width', index=0,
+ number=1, type=1, cpp_type=5, label=2,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ descriptor.FieldDescriptor(
+ name='color', full_name='LineDefProto.color', index=1,
+ number=2, type=13, cpp_type=3, label=2,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ descriptor.FieldDescriptor(
+ name='dashdot', full_name='LineDefProto.dashdot', index=2,
+ number=3, type=11, cpp_type=10, label=1,
+ has_default_value=False, default_value=None,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ descriptor.FieldDescriptor(
+ name='pathsym', full_name='LineDefProto.pathsym', index=3,
+ number=4, type=11, cpp_type=10, label=1,
+ has_default_value=False, default_value=None,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ descriptor.FieldDescriptor(
+ name='join', full_name='LineDefProto.join', index=4,
+ number=6, type=14, cpp_type=8, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ descriptor.FieldDescriptor(
+ name='cap', full_name='LineDefProto.cap', index=5,
+ number=7, type=14, cpp_type=8, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ options=None,
+ is_extendable=False,
+ extension_ranges=[],
+ serialized_start=306,
+ serialized_end=462,
+)
+
+
+_AREARULEPROTO = descriptor.Descriptor(
+ name='AreaRuleProto',
+ full_name='AreaRuleProto',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ descriptor.FieldDescriptor(
+ name='color', full_name='AreaRuleProto.color', index=0,
+ number=1, type=13, cpp_type=3, label=2,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ descriptor.FieldDescriptor(
+ name='border', full_name='AreaRuleProto.border', index=1,
+ number=2, type=11, cpp_type=10, label=1,
+ has_default_value=False, default_value=None,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ descriptor.FieldDescriptor(
+ name='priority', full_name='AreaRuleProto.priority', index=2,
+ number=3, type=5, cpp_type=1, label=2,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ options=None,
+ is_extendable=False,
+ extension_ranges=[],
+ serialized_start=464,
+ serialized_end=543,
+)
+
+
+_SYMBOLRULEPROTO = descriptor.Descriptor(
+ name='SymbolRuleProto',
+ full_name='SymbolRuleProto',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ descriptor.FieldDescriptor(
+ name='name', full_name='SymbolRuleProto.name', index=0,
+ number=1, type=9, cpp_type=9, label=2,
+ has_default_value=False, default_value=unicode("", "utf-8"),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ descriptor.FieldDescriptor(
+ name='apply_for_type', full_name='SymbolRuleProto.apply_for_type', index=1,
+ number=2, type=5, cpp_type=1, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ descriptor.FieldDescriptor(
+ name='priority', full_name='SymbolRuleProto.priority', index=2,
+ number=3, type=5, cpp_type=1, label=2,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ options=None,
+ is_extendable=False,
+ extension_ranges=[],
+ serialized_start=545,
+ serialized_end=618,
+)
+
+
+_CAPTIONDEFPROTO = descriptor.Descriptor(
+ name='CaptionDefProto',
+ full_name='CaptionDefProto',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ descriptor.FieldDescriptor(
+ name='height', full_name='CaptionDefProto.height', index=0,
+ number=1, type=5, cpp_type=1, label=2,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ descriptor.FieldDescriptor(
+ name='color', full_name='CaptionDefProto.color', index=1,
+ number=2, type=13, cpp_type=3, label=2,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ descriptor.FieldDescriptor(
+ name='stroke_color', full_name='CaptionDefProto.stroke_color', index=2,
+ number=3, type=13, cpp_type=3, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ descriptor.FieldDescriptor(
+ name='offset_x', full_name='CaptionDefProto.offset_x', index=3,
+ number=4, type=5, cpp_type=1, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ descriptor.FieldDescriptor(
+ name='offset_y', full_name='CaptionDefProto.offset_y', index=4,
+ number=5, type=5, cpp_type=1, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ options=None,
+ is_extendable=False,
+ extension_ranges=[],
+ serialized_start=620,
+ serialized_end=726,
+)
+
+
+_CAPTIONRULEPROTO = descriptor.Descriptor(
+ name='CaptionRuleProto',
+ full_name='CaptionRuleProto',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ descriptor.FieldDescriptor(
+ name='primary', full_name='CaptionRuleProto.primary', index=0,
+ number=1, type=11, cpp_type=10, label=2,
+ has_default_value=False, default_value=None,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ descriptor.FieldDescriptor(
+ name='secondary', full_name='CaptionRuleProto.secondary', index=1,
+ number=2, type=11, cpp_type=10, label=1,
+ has_default_value=False, default_value=None,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ descriptor.FieldDescriptor(
+ name='priority', full_name='CaptionRuleProto.priority', index=2,
+ number=3, type=5, cpp_type=1, label=2,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ options=None,
+ is_extendable=False,
+ extension_ranges=[],
+ serialized_start=728,
+ serialized_end=836,
+)
+
+
+_CIRCLERULEPROTO = descriptor.Descriptor(
+ name='CircleRuleProto',
+ full_name='CircleRuleProto',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ descriptor.FieldDescriptor(
+ name='radius', full_name='CircleRuleProto.radius', index=0,
+ number=1, type=1, cpp_type=5, label=2,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ descriptor.FieldDescriptor(
+ name='color', full_name='CircleRuleProto.color', index=1,
+ number=2, type=13, cpp_type=3, label=2,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ descriptor.FieldDescriptor(
+ name='border', full_name='CircleRuleProto.border', index=2,
+ number=3, type=11, cpp_type=10, label=1,
+ has_default_value=False, default_value=None,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ descriptor.FieldDescriptor(
+ name='priority', full_name='CircleRuleProto.priority', index=3,
+ number=4, type=5, cpp_type=1, label=2,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ options=None,
+ is_extendable=False,
+ extension_ranges=[],
+ serialized_start=838,
+ serialized_end=935,
+)
+
+
+_PATHTEXTRULEPROTO = descriptor.Descriptor(
+ name='PathTextRuleProto',
+ full_name='PathTextRuleProto',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ descriptor.FieldDescriptor(
+ name='primary', full_name='PathTextRuleProto.primary', index=0,
+ number=1, type=11, cpp_type=10, label=2,
+ has_default_value=False, default_value=None,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ descriptor.FieldDescriptor(
+ name='secondary', full_name='PathTextRuleProto.secondary', index=1,
+ number=2, type=11, cpp_type=10, label=1,
+ has_default_value=False, default_value=None,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ descriptor.FieldDescriptor(
+ name='priority', full_name='PathTextRuleProto.priority', index=2,
+ number=3, type=5, cpp_type=1, label=2,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ options=None,
+ is_extendable=False,
+ extension_ranges=[],
+ serialized_start=937,
+ serialized_end=1046,
+)
+
+
+_DRAWELEMENTPROTO = descriptor.Descriptor(
+ name='DrawElementProto',
+ full_name='DrawElementProto',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ descriptor.FieldDescriptor(
+ name='scale', full_name='DrawElementProto.scale', index=0,
+ number=1, type=5, cpp_type=1, label=2,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ descriptor.FieldDescriptor(
+ name='lines', full_name='DrawElementProto.lines', index=1,
+ number=2, type=11, cpp_type=10, label=3,
+ has_default_value=False, default_value=[],
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ descriptor.FieldDescriptor(
+ name='area', full_name='DrawElementProto.area', index=2,
+ number=3, type=11, cpp_type=10, label=1,
+ has_default_value=False, default_value=None,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ descriptor.FieldDescriptor(
+ name='symbol', full_name='DrawElementProto.symbol', index=3,
+ number=4, type=11, cpp_type=10, label=1,
+ has_default_value=False, default_value=None,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ descriptor.FieldDescriptor(
+ name='caption', full_name='DrawElementProto.caption', index=4,
+ number=5, type=11, cpp_type=10, label=1,
+ has_default_value=False, default_value=None,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ descriptor.FieldDescriptor(
+ name='circle', full_name='DrawElementProto.circle', index=5,
+ number=6, type=11, cpp_type=10, label=1,
+ has_default_value=False, default_value=None,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ descriptor.FieldDescriptor(
+ name='path_text', full_name='DrawElementProto.path_text', index=6,
+ number=7, type=11, cpp_type=10, label=1,
+ has_default_value=False, default_value=None,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ options=None,
+ is_extendable=False,
+ extension_ranges=[],
+ serialized_start=1049,
+ serialized_end=1286,
+)
+
+
+_CLASSIFELEMENTPROTO = descriptor.Descriptor(
+ name='ClassifElementProto',
+ full_name='ClassifElementProto',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ descriptor.FieldDescriptor(
+ name='name', full_name='ClassifElementProto.name', index=0,
+ number=1, type=9, cpp_type=9, label=2,
+ has_default_value=False, default_value=unicode("", "utf-8"),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ descriptor.FieldDescriptor(
+ name='element', full_name='ClassifElementProto.element', index=1,
+ number=2, type=11, cpp_type=10, label=3,
+ has_default_value=False, default_value=[],
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ options=None,
+ is_extendable=False,
+ extension_ranges=[],
+ serialized_start=1288,
+ serialized_end=1359,
+)
+
+
+_CONTAINERPROTO = descriptor.Descriptor(
+ name='ContainerProto',
+ full_name='ContainerProto',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ descriptor.FieldDescriptor(
+ name='cont', full_name='ContainerProto.cont', index=0,
+ number=1, type=11, cpp_type=10, label=3,
+ has_default_value=False, default_value=[],
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ options=None,
+ is_extendable=False,
+ extension_ranges=[],
+ serialized_start=1361,
+ serialized_end=1413,
+)
+
+_LINERULEPROTO.fields_by_name['dashdot'].message_type = _DASHDOTPROTO
+_LINERULEPROTO.fields_by_name['pathsym'].message_type = _PATHSYMPROTO
+_LINERULEPROTO.fields_by_name['join'].enum_type = _LINEJOIN
+_LINERULEPROTO.fields_by_name['cap'].enum_type = _LINECAP
+_LINEDEFPROTO.fields_by_name['dashdot'].message_type = _DASHDOTPROTO
+_LINEDEFPROTO.fields_by_name['pathsym'].message_type = _PATHSYMPROTO
+_LINEDEFPROTO.fields_by_name['join'].enum_type = _LINEJOIN
+_LINEDEFPROTO.fields_by_name['cap'].enum_type = _LINECAP
+_AREARULEPROTO.fields_by_name['border'].message_type = _LINEDEFPROTO
+_CAPTIONRULEPROTO.fields_by_name['primary'].message_type = _CAPTIONDEFPROTO
+_CAPTIONRULEPROTO.fields_by_name['secondary'].message_type = _CAPTIONDEFPROTO
+_CIRCLERULEPROTO.fields_by_name['border'].message_type = _LINEDEFPROTO
+_PATHTEXTRULEPROTO.fields_by_name['primary'].message_type = _CAPTIONDEFPROTO
+_PATHTEXTRULEPROTO.fields_by_name['secondary'].message_type = _CAPTIONDEFPROTO
+_DRAWELEMENTPROTO.fields_by_name['lines'].message_type = _LINERULEPROTO
+_DRAWELEMENTPROTO.fields_by_name['area'].message_type = _AREARULEPROTO
+_DRAWELEMENTPROTO.fields_by_name['symbol'].message_type = _SYMBOLRULEPROTO
+_DRAWELEMENTPROTO.fields_by_name['caption'].message_type = _CAPTIONRULEPROTO
+_DRAWELEMENTPROTO.fields_by_name['circle'].message_type = _CIRCLERULEPROTO
+_DRAWELEMENTPROTO.fields_by_name['path_text'].message_type = _PATHTEXTRULEPROTO
+_CLASSIFELEMENTPROTO.fields_by_name['element'].message_type = _DRAWELEMENTPROTO
+_CONTAINERPROTO.fields_by_name['cont'].message_type = _CLASSIFELEMENTPROTO
+DESCRIPTOR.message_types_by_name['DashDotProto'] = _DASHDOTPROTO
+DESCRIPTOR.message_types_by_name['PathSymProto'] = _PATHSYMPROTO
+DESCRIPTOR.message_types_by_name['LineRuleProto'] = _LINERULEPROTO
+DESCRIPTOR.message_types_by_name['LineDefProto'] = _LINEDEFPROTO
+DESCRIPTOR.message_types_by_name['AreaRuleProto'] = _AREARULEPROTO
+DESCRIPTOR.message_types_by_name['SymbolRuleProto'] = _SYMBOLRULEPROTO
+DESCRIPTOR.message_types_by_name['CaptionDefProto'] = _CAPTIONDEFPROTO
+DESCRIPTOR.message_types_by_name['CaptionRuleProto'] = _CAPTIONRULEPROTO
+DESCRIPTOR.message_types_by_name['CircleRuleProto'] = _CIRCLERULEPROTO
+DESCRIPTOR.message_types_by_name['PathTextRuleProto'] = _PATHTEXTRULEPROTO
+DESCRIPTOR.message_types_by_name['DrawElementProto'] = _DRAWELEMENTPROTO
+DESCRIPTOR.message_types_by_name['ClassifElementProto'] = _CLASSIFELEMENTPROTO
+DESCRIPTOR.message_types_by_name['ContainerProto'] = _CONTAINERPROTO
+
+
+class DashDotProto(message.Message):
+ __metaclass__ = reflection.GeneratedProtocolMessageType
+ DESCRIPTOR = _DASHDOTPROTO
+
+ # @@protoc_insertion_point(class_scope:DashDotProto)
+
+
+class PathSymProto(message.Message):
+ __metaclass__ = reflection.GeneratedProtocolMessageType
+ DESCRIPTOR = _PATHSYMPROTO
+
+ # @@protoc_insertion_point(class_scope:PathSymProto)
+
+
+class LineRuleProto(message.Message):
+ __metaclass__ = reflection.GeneratedProtocolMessageType
+ DESCRIPTOR = _LINERULEPROTO
+
+ # @@protoc_insertion_point(class_scope:LineRuleProto)
+
+
+class LineDefProto(message.Message):
+ __metaclass__ = reflection.GeneratedProtocolMessageType
+ DESCRIPTOR = _LINEDEFPROTO
+
+ # @@protoc_insertion_point(class_scope:LineDefProto)
+
+
+class AreaRuleProto(message.Message):
+ __metaclass__ = reflection.GeneratedProtocolMessageType
+ DESCRIPTOR = _AREARULEPROTO
+
+ # @@protoc_insertion_point(class_scope:AreaRuleProto)
+
+
+class SymbolRuleProto(message.Message):
+ __metaclass__ = reflection.GeneratedProtocolMessageType
+ DESCRIPTOR = _SYMBOLRULEPROTO
+
+ # @@protoc_insertion_point(class_scope:SymbolRuleProto)
+
+
+class CaptionDefProto(message.Message):
+ __metaclass__ = reflection.GeneratedProtocolMessageType
+ DESCRIPTOR = _CAPTIONDEFPROTO
+
+ # @@protoc_insertion_point(class_scope:CaptionDefProto)
+
+
+class CaptionRuleProto(message.Message):
+ __metaclass__ = reflection.GeneratedProtocolMessageType
+ DESCRIPTOR = _CAPTIONRULEPROTO
+
+ # @@protoc_insertion_point(class_scope:CaptionRuleProto)
+
+
+class CircleRuleProto(message.Message):
+ __metaclass__ = reflection.GeneratedProtocolMessageType
+ DESCRIPTOR = _CIRCLERULEPROTO
+
+ # @@protoc_insertion_point(class_scope:CircleRuleProto)
+
+
+class PathTextRuleProto(message.Message):
+ __metaclass__ = reflection.GeneratedProtocolMessageType
+ DESCRIPTOR = _PATHTEXTRULEPROTO
+
+ # @@protoc_insertion_point(class_scope:PathTextRuleProto)
+
+
+class DrawElementProto(message.Message):
+ __metaclass__ = reflection.GeneratedProtocolMessageType
+ DESCRIPTOR = _DRAWELEMENTPROTO
+
+ # @@protoc_insertion_point(class_scope:DrawElementProto)
+
+
+class ClassifElementProto(message.Message):
+ __metaclass__ = reflection.GeneratedProtocolMessageType
+ DESCRIPTOR = _CLASSIFELEMENTPROTO
+
+ # @@protoc_insertion_point(class_scope:ClassifElementProto)
+
+
+class ContainerProto(message.Message):
+ __metaclass__ = reflection.GeneratedProtocolMessageType
+ DESCRIPTOR = _CONTAINERPROTO
+
+ # @@protoc_insertion_point(class_scope:ContainerProto)
+
+# @@protoc_insertion_point(module_scope)
diff --git a/tools/kothic/libkomwm.py b/tools/kothic/libkomwm.py
new file mode 100644
index 0000000000..d14d60f91b
--- /dev/null
+++ b/tools/kothic/libkomwm.py
@@ -0,0 +1,438 @@
+from drules_struct_pb2 import *
+from timer import *
+from mapcss import MapCSS
+from optparse import OptionParser
+import os
+import csv
+import sys
+import json
+import mapcss.webcolors
+whatever_to_hex = mapcss.webcolors.webcolors.whatever_to_hex
+whatever_to_cairo = mapcss.webcolors.webcolors.whatever_to_cairo
+
+WIDTH_SCALE = 1.0
+
+def komap_mapswithme(options, style, filename):
+ if options.outfile == "-":
+ print "Please specify base output path."
+ exit()
+ else:
+ ddir = os.path.dirname(options.outfile)
+
+ types_file = open(os.path.join(ddir, 'types.txt'), "w")
+ drules_bin = open(os.path.join(options.outfile + '.bin'), "wb")
+ drules_txt = open(os.path.join(options.outfile + '.txt'), "wb")
+
+ drules = ContainerProto()
+ classificator = {}
+ class_order = []
+ class_tree = {}
+ visibility = {}
+ textures = {}
+
+ for row in csv.reader(open(os.path.join(ddir, 'mapcss-mapping.csv')), delimiter=';'):
+ pairs = [i.strip(']').split("=") for i in row[1].split(',')[0].split('[')]
+ kv = {}
+ for i in pairs:
+ if len(i) == 1:
+ if i[0]:
+ if i[0][0] == "!":
+ kv[i[0][1:].strip('?')] = "no"
+ else:
+ kv[i[0].strip('?')] = "yes"
+ else:
+ kv[i[0]] = i[1]
+ classificator[row[0].replace("|", "-")] = kv
+ if row[2] != "x":
+ class_order.append(row[0].replace("|", "-"))
+ print >> types_file, row[0]
+ else:
+ # compatibility mode
+ if row[6]:
+ print >> types_file, row[6]
+ else:
+ print >> types_file, "mapswithme"
+ class_tree[row[0].replace("|", "-")] = row[0]
+ class_order.sort()
+ types_file.close()
+
+ def mwm_encode_color(st, prefix='', default='black'):
+ if prefix:
+ prefix += "-"
+ opacity = hex(255 - int(255 * float(st.get(prefix + "opacity", 1))))
+ color = whatever_to_hex(st.get(prefix + 'color', default))
+ color = color[1] + color[1] + color[3] + color[3] + color[5] + color[5]
+ return int(opacity + color, 16)
+
+ def mwm_encode_image(st, prefix='icon', bgprefix='symbol'):
+ if prefix:
+ prefix += "-"
+ if bgprefix:
+ bgprefix += "-"
+ if prefix + "image" not in st:
+ return False
+ # strip last ".svg"
+ handle = st.get(prefix + "image")[:-4]
+ return handle, handle
+
+ bgpos = 0
+
+ dr_linecaps = {'none': BUTTCAP, 'butt': BUTTCAP, 'round': ROUNDCAP}
+ dr_linejoins = {'none': NOJOIN, 'bevel': BEVELJOIN, 'round': ROUNDJOIN}
+
+ # atbuild = AccumulativeTimer()
+ # atzstyles = AccumulativeTimer()
+ # atdrcont = AccumulativeTimer()
+ # atline = AccumulativeTimer()
+ # atarea = AccumulativeTimer()
+ # atnode = AccumulativeTimer()
+
+ # atbuild.Start()
+
+ for cl in class_order:
+ clname = cl if cl.find('-') == -1 else cl[:cl.find('-')]
+ # clname = cl
+ style.build_choosers_tree(clname, "line", classificator[cl])
+ style.build_choosers_tree(clname, "area", classificator[cl])
+ style.build_choosers_tree(clname, "node", classificator[cl])
+
+ style.restore_choosers_order("line");
+ style.restore_choosers_order("area");
+ style.restore_choosers_order("node");
+
+ # atbuild.Stop()
+
+ for cl in class_order:
+ visstring = ["0"] * (options.maxzoom - options.minzoom + 1)
+
+ clname = cl if cl.find('-') == -1 else cl[:cl.find('-')]
+ # clname = cl
+ txclass = classificator[cl]
+ txclass["name"] = "name"
+ txclass["addr:housenumber"] = "addr:housenumber"
+ txclass["ref"] = "ref"
+ txclass["int_name"] = "int_name"
+ txclass["addr:flats"] = "addr:flats"
+
+ prev_area_len = -1
+ prev_node_len = -1
+ prev_line_len = -1
+ check_area = True
+ check_node = True
+ check_line = True
+
+ # atzstyles.Start()
+
+ zstyles_arr = [None] * (options.maxzoom - options.minzoom + 1)
+ has_icons_for_areas_arr = [False] * (options.maxzoom - options.minzoom + 1)
+
+ for zoom in xrange(options.maxzoom, options.minzoom - 1, -1):
+ has_icons_for_areas = False
+ zstyle = {}
+
+ if check_line:
+ if "area" not in txclass:
+ # atline.Start()
+ linestyle = style.get_style_dict(clname, "line", txclass, zoom, olddict=zstyle, cache=False)
+ if prev_line_len == -1:
+ prev_line_len = len(linestyle)
+ if len(linestyle) == 0:
+ if prev_line_len != 0:
+ check_line = False
+ zstyle = linestyle
+ # atline.Stop()
+
+ if check_area:
+ # atarea.Start()
+ areastyle = style.get_style_dict(clname, "area", txclass, zoom, olddict=zstyle, cache=False)
+ for st in areastyle.values():
+ if "icon-image" in st or 'symbol-shape' in st or 'symbol-image' in st:
+ has_icons_for_areas = True
+ break
+ if prev_area_len == -1:
+ prev_area_len = len(areastyle)
+ if len(areastyle) == 0:
+ if prev_area_len != 0:
+ check_area = False
+ zstyle = areastyle
+ # atarea.Stop()
+
+ if check_node:
+ if "area" not in txclass:
+ # atnode.Start()
+ nodestyle = style.get_style_dict(clname, "node", txclass, zoom, olddict=zstyle, cache=False)
+ if prev_node_len == -1:
+ prev_node_len = len(nodestyle)
+ if len(nodestyle) == 0:
+ if prev_node_len != 0:
+ check_node = False
+ zstyle = nodestyle
+ # atnode.Stop()
+
+ if not check_line and not check_area and not check_node:
+ break
+
+ zstyle = zstyle.values()
+
+ zstyles_arr[zoom - options.minzoom] = zstyle
+ has_icons_for_areas_arr[zoom - options.minzoom]= has_icons_for_areas
+
+ # atzstyles.Stop()
+
+ # atdrcont.Start()
+
+ dr_cont = ClassifElementProto()
+ dr_cont.name = cl
+ for zoom in xrange(options.minzoom, options.maxzoom + 1):
+ zstyle = zstyles_arr[zoom - options.minzoom]
+ if zstyle is None or len(zstyle) == 0:
+ continue
+ has_icons_for_areas = has_icons_for_areas_arr[zoom - options.minzoom]
+
+ has_lines = False
+ has_icons = False
+ has_fills = False
+ for st in zstyle:
+ st = dict([(k, v) for k, v in st.iteritems() if str(v).strip(" 0.")])
+ if 'width' in st or 'pattern-image' in st:
+ has_lines = True
+ if 'icon-image' in st or 'symbol-shape' in st or 'symbol-image' in st:
+ has_icons = True
+ if 'fill-color' in st:
+ has_fills = True
+
+ has_text = None
+ txfmt = []
+ for st in zstyle:
+ if st.get('text') and not st.get('text') in txfmt:
+ txfmt.append(st.get('text'))
+ if has_text is None:
+ has_text = []
+ has_text.append(st)
+
+ if has_lines or has_text or has_fills or has_icons:
+ visstring[zoom] = "1"
+ dr_element = DrawElementProto()
+ dr_element.scale = zoom
+
+ for st in zstyle:
+ if st.get('-x-kot-layer') == 'top':
+ st['z-index'] = float(st.get('z-index', 0)) + 15001.
+ elif st.get('-x-kot-layer') == 'bottom':
+ st['z-index'] = float(st.get('z-index', 0)) - 15001.
+
+ if st.get('casing-width') not in (None, 0): # and (st.get('width') or st.get('fill-color')):
+ if st.get('casing-linecap', 'butt') == 'butt':
+ dr_line = LineRuleProto()
+ dr_line.width = (st.get('width', 0) * WIDTH_SCALE) + (st.get('casing-width') * WIDTH_SCALE * 2)
+ dr_line.color = mwm_encode_color(st, "casing")
+ dr_line.priority = min(int(st.get('z-index', 0) + 999), 20000)
+ dashes = st.get('casing-dashes', st.get('dashes', []))
+ dr_line.dashdot.dd.extend(dashes)
+ dr_line.cap = dr_linecaps.get(st.get('casing-linecap', 'butt'), BUTTCAP)
+ dr_line.join = dr_linejoins.get(st.get('casing-linejoin', 'round'), ROUNDJOIN)
+ dr_element.lines.extend([dr_line])
+
+ # Let's try without this additional line style overhead. Needed only for casing in road endings.
+ # if st.get('casing-linecap', st.get('linecap', 'round')) != 'butt':
+ # dr_line = LineRuleProto()
+ # dr_line.width = (st.get('width', 0) * WIDTH_SCALE) + (st.get('casing-width') * WIDTH_SCALE * 2)
+ # dr_line.color = mwm_encode_color(st, "casing")
+ # dr_line.priority = -15000
+ # dashes = st.get('casing-dashes', st.get('dashes', []))
+ # dr_line.dashdot.dd.extend(dashes)
+ # dr_line.cap = dr_linecaps.get(st.get('casing-linecap', 'round'), ROUNDCAP)
+ # dr_line.join = dr_linejoins.get(st.get('casing-linejoin', 'round'), ROUNDJOIN)
+ # dr_element.lines.extend([dr_line])
+
+ if has_lines:
+ if st.get('width'):
+ dr_line = LineRuleProto()
+ dr_line.width = (st.get('width', 0) * WIDTH_SCALE)
+ dr_line.color = mwm_encode_color(st)
+ for i in st.get('dashes', []):
+ dr_line.dashdot.dd.extend([max(float(i), 1) * WIDTH_SCALE])
+ dr_line.cap = dr_linecaps.get(st.get('linecap', 'butt'), BUTTCAP)
+ dr_line.join = dr_linejoins.get(st.get('linejoin', 'round'), ROUNDJOIN)
+ dr_line.priority = min((int(st.get('z-index', 0)) + 1000), 20000)
+ dr_element.lines.extend([dr_line])
+ if st.get('pattern-image'):
+ dr_line = LineRuleProto()
+ dr_line.width = 0
+ dr_line.color = 0
+ icon = mwm_encode_image(st, prefix='pattern')
+ dr_line.pathsym.name = icon[0]
+ dr_line.pathsym.step = float(st.get('pattern-spacing', 0)) - 16
+ dr_line.pathsym.offset = st.get('pattern-offset', 0)
+ dr_line.priority = int(st.get('z-index', 0)) + 1000
+ dr_element.lines.extend([dr_line])
+ textures[icon[0]] = icon[1]
+
+ if has_icons:
+ if st.get('icon-image'):
+ if not has_icons_for_areas:
+ dr_element.symbol.apply_for_type = 1
+ icon = mwm_encode_image(st)
+ dr_element.symbol.name = icon[0]
+ dr_element.symbol.priority = min(19100, (16000 + int(st.get('z-index', 0))))
+ textures[icon[0]] = icon[1]
+ has_icons = False
+ if st.get('symbol-shape'):
+ dr_element.circle.radius = float(st.get('symbol-size'))
+ dr_element.circle.color = mwm_encode_color(st, 'symbol-fill')
+ dr_element.circle.priority = min(19000, (14000 + int(st.get('z-index', 0))))
+ has_icons = False
+
+ if has_text and st.get('text'):
+ has_text = has_text[:2]
+ has_text.reverse()
+ dr_text = dr_element.path_text
+ base_z = 15000
+ if st.get('text-position', 'center') == 'line':
+ dr_text = dr_element.path_text
+ base_z = 16000
+ else:
+ dr_text = dr_element.caption
+ for sp in has_text[:]:
+ dr_cur_subtext = dr_text.primary
+ if len(has_text) == 2:
+ dr_cur_subtext = dr_text.secondary
+ dr_cur_subtext.height = int(float(sp.get('font-size', "10").split(",")[0]))
+ dr_cur_subtext.color = mwm_encode_color(sp, "text")
+ if st.get('text-halo-radius', 0) != 0:
+ dr_cur_subtext.stroke_color = mwm_encode_color(sp, "text-halo", "white")
+ if 'text-offset' in sp or 'text-offset-y' in sp:
+ dr_cur_subtext.offset_y = int(sp.get('text-offset-y', sp.get('text-offset', 0)))
+ if 'text-offset-x' in sp:
+ dr_cur_subtext.offset_x = int(sp.get('text-offset-x', 0))
+ has_text.pop()
+ dr_text.priority = min(19000, (base_z + int(st.get('z-index', 0))))
+ has_text = None
+
+ if has_fills:
+ if ('fill-color' in st) and (float(st.get('fill-opacity', 1)) > 0):
+ dr_element.area.color = mwm_encode_color(st, "fill")
+ if st.get('fill-position', 'foreground') == 'background':
+ if 'z-index' not in st:
+ bgpos -= 1
+ dr_element.area.priority = bgpos - 16000
+ else:
+ zzz = int(st.get('z-index', 0))
+ if zzz > 0:
+ dr_element.area.priority = zzz - 16000
+ else:
+ dr_element.area.priority = zzz - 16700
+ else:
+ dr_element.area.priority = (int(st.get('z-index', 0)) + 1 + 1000)
+ has_fills = False
+
+ dr_cont.element.extend([dr_element])
+
+ if dr_cont.element:
+ drules.cont.extend([dr_cont])
+
+ # atdrcont.Stop()
+
+ visibility["world|" + class_tree[cl] + "|"] = "".join(visstring)
+
+ # atwrite = AccumulativeTimer()
+ # atwrite.Start()
+
+ drules_bin.write(drules.SerializeToString())
+ drules_txt.write(unicode(drules))
+
+ visnodes = set()
+ for k, v in visibility.iteritems():
+ vis = k.split("|")
+ for i in range(1, len(vis) - 1):
+ visnodes.add("|".join(vis[0:i]) + "|")
+ viskeys = list(set(visibility.keys() + list(visnodes)))
+
+ def cmprepl(a, b):
+ if a == b:
+ return 0
+ a = a.replace("|", "-")
+ b = b.replace("|", "-")
+ if a > b:
+ return 1
+ return -1
+ viskeys.sort(cmprepl)
+
+ visibility_file = open(os.path.join(ddir, 'visibility.txt'), "w")
+ classificator_file = open(os.path.join(ddir, 'classificator.txt'), "w")
+
+ oldoffset = ""
+ for k in viskeys:
+ offset = " " * (k.count("|") - 1)
+ for i in range(len(oldoffset) / 4, len(offset) / 4, -1):
+ print >> visibility_file, " " * i + "{}"
+ print >> classificator_file, " " * i + "{}"
+
+ oldoffset = offset
+ end = "-"
+ if k in visnodes:
+ end = "+"
+ print >> visibility_file, offset + k.split("|")[-2] + " " + visibility.get(k, "0" * (options.maxzoom + 1)) + " " + end
+ print >> classificator_file, offset + k.split("|")[-2] + " " + end
+ for i in range(len(offset) / 4, 0, -1):
+ print >> visibility_file, " " * i + "{}"
+ print >> classificator_file, " " * i + "{}"
+
+ # atwrite.Stop()
+
+ # print "build, sec: %s" % (atbuild.ElapsedSec())
+ # print "zstyle %s times, sec: %s" % (atzstyles.Count(), atzstyles.ElapsedSec())
+ # print "drcont %s times, sec: %s" % (atdrcont.Count(), atdrcont.ElapsedSec())
+ # print "line %s times, sec: %s" % (atline.Count(), atline.ElapsedSec())
+ # print "area %s times, sec: %s" % (atarea.Count(), atarea.ElapsedSec())
+ # print "node %s times, sec: %s" % (atnode.Count(), atnode.ElapsedSec())
+ # print "writing files, sec: %s" % (atwrite.ElapsedSec())
+
+# Main
+
+parser = OptionParser()
+parser.add_option("-s", "--stylesheet", dest="filename",
+ help="read MapCSS stylesheet from FILE", metavar="FILE")
+parser.add_option("-f", "--minzoom", dest="minzoom", default=0, type="int",
+ help="minimal available zoom level", metavar="ZOOM")
+parser.add_option("-t", "--maxzoom", dest="maxzoom", default=19, type="int",
+ help="maximal available zoom level", metavar="ZOOM")
+parser.add_option("-l", "--locale", dest="locale",
+ help="language that should be used for labels (ru, en, be, uk..)", metavar="LANG")
+parser.add_option("-o", "--output-file", dest="outfile", default="-",
+ help="output filename (defaults to stdout)", metavar="FILE")
+parser.add_option("-p", "--osm2pgsql-style", dest="osm2pgsqlstyle", default="-",
+ help="osm2pgsql stylesheet filename", metavar="FILE")
+parser.add_option("-b", "--background-only", dest="bgonly", action="store_true", default=False,
+ help="Skip rendering of icons and labels", metavar="BOOL")
+parser.add_option("-T", "--text-scale", dest="textscale", default=1, type="float",
+ help="text size scale", metavar="SCALE")
+parser.add_option("-c", "--config", dest="conffile", default="komap.conf",
+ help="config file name", metavar="FILE")
+
+(options, args) = parser.parse_args()
+
+if (options.filename is None):
+ parser.error("MapCSS stylesheet filename is required")
+
+try:
+ # atparse = AccumulativeTimer()
+ # atbuild = AccumulativeTimer()
+
+ # atparse.Start()
+ style = MapCSS(options.minzoom, options.maxzoom + 1) # zoom levels
+ style.parse(filename = options.filename)
+ # atparse.Stop()
+
+ # atbuild.Start()
+ komap_mapswithme(options, style, options.filename)
+ # atbuild.Stop()
+
+ # print "mapcss parse, sec: %s" % (atparse.ElapsedSec())
+ # print "build, sec: %s" % (atbuild.ElapsedSec())
+
+ exit(0)
+
+except Exception as e:
+ print >> sys.stderr, "Error\n" + str(e)
+ exit(-1)
diff --git a/tools/kothic/mapcss/Condition.py b/tools/kothic/mapcss/Condition.py
new file mode 100644
index 0000000000..a476eb9379
--- /dev/null
+++ b/tools/kothic/mapcss/Condition.py
@@ -0,0 +1,329 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# This file is part of kothic, the realtime map renderer.
+
+# kothic 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.
+
+# kothic 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 kothic. If not, see .
+
+import re
+
+INVERSIONS = {"eq": "ne", "true": "false", "set": "unset", "<": ">=", ">": "<="}
+in2 = {}
+for a, b in INVERSIONS.iteritems():
+ in2[b] = a
+INVERSIONS.update(in2)
+del in2
+
+# Fast conditions
+
+class EqConditionDD:
+ def __init__(self, params):
+ self.value = params[1]
+ def extract_tags(self):
+ return set(["*"])
+ def test(self, tags):
+ return self.value
+
+class EqCondition:
+ def __init__(self, params):
+ self.tag = params[0]
+ self.value = params[1]
+ def extract_tags(self):
+ return set([self.tag])
+ def test(self, tags):
+ if self.tag in tags:
+ return tags[self.tag] == self.value
+ else:
+ return False
+
+class NotEqCondition:
+ def __init__(self, params):
+ self.tag = params[0]
+ self.value = params[1]
+ def extract_tags(self):
+ return set([self.tag])
+ def test(self, tags):
+ if self.tag in tags:
+ return tags[self.tag] != self.value
+ else:
+ return False
+
+class SetCondition:
+ def __init__(self, params):
+ self.tag = params[0]
+ def extract_tags(self):
+ return set([self.tag])
+ def test(self, tags):
+ if self.tag in tags:
+ return tags[self.tag] != ''
+ return False
+
+class UnsetCondition:
+ def __init__(self, params):
+ self.tag = params[0]
+ def extract_tags(self):
+ return set([self.tag])
+ def test(self, tags):
+ if self.tag in tags:
+ return tags[self.tag] == ''
+ return True
+
+class TrueCondition:
+ def __init__(self, params):
+ self.tag = params[0]
+ def extract_tags(self):
+ return set([self.tag])
+ def test(self, tags):
+ if self.tag in tags:
+ return tags[self.tag] == 'yes'
+ return False
+
+class UntrueCondition:
+ def __init__(self, params):
+ self.tag = params[0]
+ def extract_tags(self):
+ return set([self.tag])
+ def test(self, tags):
+ if self.tag in tags:
+ return tags[self.tag] == 'no'
+ return False
+
+# Slow condition
+
+class Condition:
+ def __init__(self, typez, params):
+ self.type = typez # eq, regex, lt, gt etc.
+ if type(params) == type(str()):
+ params = (params,)
+ self.params = params # e.g. ('highway','primary')
+ if typez == "regex":
+ self.regex = re.compile(self.params[0], re.I)
+ self.compiled_regex = ""
+
+ def get_interesting_tags(self):
+ if self.params[0][:2] == "::":
+ return []
+ return set([self.params[0]])
+
+ def extract_tags(self):
+ if self.params[0][:2] == "::" or self.type == "regex":
+ return set(["*"]) # unknown
+ return set([self.params[0]])
+
+ def get_numerics(self):
+ if self.type in ("<", ">", ">=", "<="):
+ return self.params[0]
+ else:
+ return False
+
+ def test(self, tags):
+ """
+ Test a hash against this condition
+ """
+ t = self.type
+ params = self.params
+ if t == 'eq': # don't compare tags against sublayers
+ if params[0][:2] == "::":
+ return params[1]
+ try:
+ if t == 'eq':
+ return tags[params[0]] == params[1]
+ if t == 'ne':
+ return tags.get(params[0], "") != params[1]
+ if t == 'regex':
+ return bool(self.regex.match(tags[params[0]]))
+ if t == 'true':
+ return tags.get(params[0]) == 'yes'
+ if t == 'untrue':
+ return tags.get(params[0]) == 'no'
+ if t == 'set':
+ if params[0] in tags:
+ return tags[params[0]] != ''
+ return False
+ if t == 'unset':
+ if params[0] in tags:
+ return tags[params[0]] == ''
+ return True
+
+ if t == '<':
+ return (Number(tags[params[0]]) < Number(params[1]))
+ if t == '<=':
+ return (Number(tags[params[0]]) <= Number(params[1]))
+ if t == '>':
+ return (Number(tags[params[0]]) > Number(params[1]))
+ if t == '>=':
+ return (Number(tags[params[0]]) >= Number(params[1]))
+ except KeyError:
+ pass
+ return False
+
+ def inverse(self):
+ """
+ Get a not-A for condition A
+ """
+ t = self.type
+ params = self.params
+ try:
+ return Condition(INVERSIONS[t], params)
+ if t == 'regex':
+ ### FIXME: learn how to invert regexes
+ return Condition("regex", params)
+ except KeyError:
+ pass
+ return self
+
+ def get_sql(self):
+ # params = [re.escape(x) for x in self.params]
+ params = self.params
+ t = self.type
+ if t == 'eq': # don't compare tags against sublayers
+ if params[0][:2] == "::":
+ return ("", "")
+ try:
+ if t == 'eq':
+ return params[0], '"%s" = \'%s\'' % (params[0], params[1])
+ if t == 'ne':
+ return params[0], '("%s" != \'%s\' or "%s" IS NULL)' % (params[0], params[1], params[0])
+ if t == 'regex':
+ return params[0], '"%s" ~ \'%s\'' % (params[0], params[1].replace("'", "\\'"))
+ if t == 'true':
+ return params[0], '"%s" = \'yes\'' % (params[0])
+ if t == 'untrue':
+ return params[0], '"%s" = \'no\'' % (params[0])
+ if t == 'set':
+ return params[0], '"%s" IS NOT NULL' % (params[0])
+ if t == 'unset':
+ return params[0], '"%s" IS NULL' % (params[0])
+
+ if t == '<':
+ return params[0], """(CASE WHEN "%s" ~ E'^[-]?[[:digit:]]+([.][[:digit:]]+)?$' THEN CAST ("%s" AS FLOAT) < %s ELSE false END) """ % (params[0], params[0], params[1])
+ if t == '<=':
+ return params[0], """(CASE WHEN "%s" ~ E'^[-]?[[:digit:]]+([.][[:digit:]]+)?$' THEN CAST ("%s" AS FLOAT) <= %s ELSE false END)""" % (params[0], params[0], params[1])
+ if t == '>':
+ return params[0], """(CASE WHEN "%s" ~ E'^[-]?[[:digit:]]+([.][[:digit:]]+)?$' THEN CAST ("%s" AS FLOAT) > %s ELSE false END) """ % (params[0], params[0], params[1])
+ if t == '>=':
+ return params[0], """(CASE WHEN "%s" ~ E'^[-]?[[:digit:]]+([.][[:digit:]]+)?$' THEN CAST ("%s" AS FLOAT) >= %s ELSE false END) """ % (params[0], params[0], params[1])
+ except KeyError:
+ pass
+
+ def get_mapnik_filter(self):
+ # params = [re.escape(x) for x in self.params]
+ params = self.params
+ t = self.type
+ if t == 'eq': # don't compare tags against sublayers
+ if params[0][:2] == "::":
+ return ''
+ try:
+ if t == 'eq':
+ return '[%s] = \'%s\'' % (params[0], params[1])
+ if t == 'ne':
+ return 'not([%s] = \'%s\')' % (params[0], params[1])
+ if t == 'regex':
+ return '[%s].match(\'%s\')' % (params[0], params[1].replace("'", "\\'"))
+ if t == 'true':
+ return '[%s] = \'yes\'' % (params[0])
+ if t == 'untrue':
+ return '[%s] = \'no\'' % (params[0])
+ if t == 'set':
+ return '[%s] != \'\'' % (params[0])
+ if t == 'unset':
+ return 'not([%s] != \'\')' % (params[0])
+
+ if t == '<':
+ return '[%s__num] < %s' % (params[0], float(params[1]))
+ if t == '<=':
+ return '[%s__num] <= %s' % (params[0], float(params[1]))
+ if t == '>':
+ return '[%s__num] > %s' % (params[0], float(params[1]))
+ if t == '>=':
+ return '[%s__num] >= %s' % (params[0], float(params[1]))
+ # return ""
+ except KeyError:
+ pass
+
+ def __repr__(self):
+ return "%s %s " % (self.type, repr(self.params))
+
+ def __eq__(self, a):
+ return (self.params == a.params) and (self.type == a.type)
+
+ def and_with(self, c2):
+ """
+ merges two rules with AND.
+ """
+ # TODO: possible other minimizations
+ if c2.params[0] == self.params[0]:
+ if c2.params == self.params:
+ if c2.type == INVERSIONS[self.type]: # for example, eq AND ne = 0
+ return False
+ if c2.type == self.type:
+ return (self,)
+
+ if self.type == ">=" and c2.type == "<=": # a<=2 and a>=2 --> a=2
+ return (Condition("eq", self.params),)
+ if self.type == "<=" and c2.type == ">=":
+ return (Condition("eq", self.params),)
+ if self.type == ">" and c2.type == "<":
+ return False
+ if self.type == "<" and c2.type == ">":
+ return False
+
+ if c2.type == "eq" and self.type in ("ne", "<", ">"):
+ if c2.params[1] != self.params[1]:
+ return (c2,)
+ if self.type == "eq" and c2.type in ("ne", "<", ">"):
+ if c2.params[1] != self.params[1]:
+ return (self,)
+ if self.type == "eq" and c2.type == "eq":
+ if c2.params[1] != self.params[1]:
+ return False
+ if c2.type == "set" and self.type in ("eq", "ne", "regex", "<", "<=", ">", ">="): # a is set and a == b -> a == b
+ return (self,)
+ if c2.type == "unset" and self.type in ("eq", "ne", "regex", "<", "<=", ">", ">="): # a is unset and a == b -> impossible
+ return False
+ if self.type == "set" and c2.type in ("eq", "ne", "regex", "<", "<=", ">", ">="):
+ return (c2,)
+ if self.type == "unset" and c2.type in ("eq", "ne", "regex", "<", "<=", ">", ">="):
+ return False
+
+ return self, c2
+
+def Number(tt):
+ """
+ Wrap float() not to produce exceptions
+ """
+ try:
+ return float(tt)
+ except ValueError:
+ return 0
+
+# Some conditions we can optimize by using "python polymorthism"
+
+def OptimizeCondition(condition):
+ if (condition.type == "eq"):
+ if (condition.params[0][:2] == "::"):
+ return EqConditionDD(condition.params)
+ else:
+ return EqCondition(condition.params)
+ elif (condition.type == "ne"):
+ return NotEqCondition(condition.params)
+ elif (condition.type == "set"):
+ return SetCondition(condition.params)
+ elif (condition.type == "unset"):
+ return UnsetCondition(condition.params)
+ elif (condition.type == "true"):
+ return TrueCondition(condition.params)
+ elif (condition.type == "untrue"):
+ return UntrueCondition(condition.params)
+ else:
+ return condition
diff --git a/tools/kothic/mapcss/Eval.py b/tools/kothic/mapcss/Eval.py
new file mode 100644
index 0000000000..3c32832d07
--- /dev/null
+++ b/tools/kothic/mapcss/Eval.py
@@ -0,0 +1,183 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# This file is part of kothic, the realtime map renderer.
+
+# kothic 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.
+
+# kothic 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 kothic. If not, see .
+
+NONE = ""
+
+
+class Eval():
+ def __init__(self, s='eval()'):
+ """
+ Parse expression and convert it into Python
+ """
+ s = s.strip()[5:-1].strip()
+ self.expr_text = s
+ try:
+ self.expr = compile(s, "MapCSS expression", "eval")
+ except:
+ # print "Can't compile %s" % s
+ self.expr = compile("0", "MapCSS expression", "eval")
+
+ def extract_tags(self):
+ """
+ Extracts list of tags that might be used in calculation
+ """
+ def fake_compute(*x):
+ """
+ Perform a fake computation. Always computes all the parameters, always returns 0.
+ WARNING: Might not cope with complex statements.
+ """
+ for t in x:
+ q = x
+ return 0
+
+ # print self.expr_text
+ tags = set([])
+ a = eval(self.expr, {}, {
+ "tag": lambda x: max([tags.add(x), 0]),
+ "prop": lambda x: 0,
+ "num": lambda x: 0,
+ "metric": fake_compute,
+ "zmetric": fake_compute,
+ "str": lambda x: "",
+ "any": fake_compute,
+ "min": fake_compute,
+ "max": fake_compute,
+ })
+ return tags
+
+ def compute(self, tags={}, props={}, xscale=1., zscale=0.5):
+ """
+ Compute this eval()
+ """
+ """
+ for k, v in tags.iteritems():
+ try:
+ tags[k] = float(v)
+ except:
+ pass
+ """
+ try:
+ return str(eval(self.expr, {}, {
+ "tag": lambda x: tags.get(x, ""),
+ "prop": lambda x: props.get(x, ""),
+ "num": m_num,
+ "metric": lambda x: m_metric(x, xscale),
+ "zmetric": lambda x: m_metric(x, zscale),
+ "str": str,
+ "any": m_any,
+ "min": m_min,
+ "max": m_max,
+ "cond": m_cond,
+ "boolean": m_boolean
+ }))
+ except:
+ return ""
+
+ def __repr__(self):
+ return "eval(%s)" % self.expr_text
+
+
+def m_boolean(expr):
+ expr = str(expr)
+ if expr in ("", "0", "no", "false", "False"):
+ return False
+ else:
+ return True
+
+
+def m_cond(why, yes, no):
+ if m_boolean(why):
+ return yes
+ else:
+ return no
+
+
+def m_min(*x):
+ """
+ min() MapCSS Feature
+ """
+ try:
+ return min([m_num(t) for t in x])
+ except:
+ return 0
+
+
+def m_max(*x):
+ """
+ max() MapCSS Feature
+ """
+ try:
+ return max([m_num(t) for t in x])
+ except:
+ return 0
+
+
+def m_any(*x):
+ """
+ any() MapCSS feature
+ """
+ for t in x:
+ if t:
+ return t
+ else:
+ return ""
+
+
+def m_num(x):
+ """
+ num() MapCSS feature
+ """
+ try:
+ return float(str(x))
+ except ValueError:
+ return 0
+
+
+def m_metric(x, t):
+ """
+ metric() and zmetric() function.
+ """
+ x = str(x)
+ try:
+ return float(x) * float(t)
+ except:
+ "Heuristics."
+ # FIXME: add ft, m and friends
+ x = x.strip()
+ try:
+ if x[-2:] in ("cm", "CM", "см"):
+ return float(x[0:-2]) * float(t) / 100
+ if x[-2:] in ("mm", "MM", "мм"):
+ return float(x[0:-2]) * float(t) / 1000
+ if x[-1] in ("m", "M", "м"):
+ return float(x[0:-1]) * float(t)
+ except:
+ return ""
+
+
+# def str(x):
+ #"""
+ # str() MapCSS feature
+ #"""
+ # return __builtins__.str(x)
+
+
+if __name__ == "__main__":
+ a = Eval(""" eval( any( metric(tag("height")), metric ( num(tag("building:levels")) * 3), metric("1m"))) """)
+ print repr(a)
+ print a.compute({"building:levels": "3"})
+ print a.extract_tags()
diff --git a/tools/kothic/mapcss/Rule.py b/tools/kothic/mapcss/Rule.py
new file mode 100644
index 0000000000..e2858d86c0
--- /dev/null
+++ b/tools/kothic/mapcss/Rule.py
@@ -0,0 +1,129 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# This file is part of kothic, the realtime map renderer.
+
+# kothic 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.
+
+# kothic 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 kothic. If not, see .
+
+type_matches = {
+ "": ('area', 'line', 'way', 'node'),
+ "area": ("area", "way"),
+ "node": ("node",),
+ "way": ("line", "area", "way"),
+ "line": ("line", "area"),
+ }
+
+class Rule():
+ def __init__(self, s=''):
+ self.conditions = []
+ # self.isAnd = True
+ self.minZoom = 0
+ self.maxZoom = 19
+ if s == "*":
+ s = ""
+ self.subject = s # "", "way", "node" or "relation"
+
+ def __repr__(self):
+ return "%s|z%s-%s %s" % (self.subject, self.minZoom, self.maxZoom, self.conditions)
+
+ def test(self, obj, tags, zoom):
+ if (zoom < self.minZoom) or (zoom > self.maxZoom):
+ return False
+
+ if (self.subject != '') and not _test_feature_compatibility(obj, self.subject, tags):
+ return False
+
+ subpart = "::default"
+ for condition in self.conditions:
+ res = condition.test(tags)
+ if not res:
+ return False
+ if type(res) != bool:
+ subpart = res
+ return subpart
+
+ def test_zoom(self, zoom):
+ return (zoom >= self.minZoom) and (zoom <= self.maxZoom)
+
+ def get_compatible_types(self):
+ return type_matches.get(self.subject, (self.subject,))
+
+ def get_interesting_tags(self, obj, zoom):
+ if obj:
+ if (self.subject != '') and not _test_feature_compatibility(obj, self.subject, {}):
+ return set()
+
+ if zoom and not self.test_zoom(zoom):
+ return set()
+
+ a = set()
+ for condition in self.conditions:
+ a.update(condition.get_interesting_tags())
+ return a
+
+ def extract_tags(self):
+ a = set()
+ for condition in self.conditions:
+ a.update(condition.extract_tags())
+ if "*" in a:
+ break
+ return a
+
+ def get_numerics(self):
+ a = set()
+ for condition in self.conditions:
+ a.add(condition.get_numerics())
+ a.discard(False)
+ return a
+
+ def get_sql_hints(self, obj, zoom):
+ if obj:
+ if (self.subject != '') and not _test_feature_compatibility(obj, self.subject, {":area": "yes"}):
+ return set()
+ if not self.test_zoom(zoom):
+ return set()
+ a = set()
+ b = set()
+ for condition in self.conditions:
+ q = condition.get_sql()
+ if q:
+ if q[1]:
+ a.add(q[0])
+ b.add(q[1])
+ b = " AND ".join(b)
+ return a, b
+
+
+def _test_feature_compatibility(f1, f2, tags={}):
+ """
+ Checks if feature of type f1 is compatible with f2.
+ """
+ if f2 == f1:
+ return True
+ if f2 not in ("way", "area", "line"):
+ return False
+ elif f2 == "way" and f1 == "line":
+ return True
+ elif f2 == "way" and f1 == "area":
+ return True
+ elif f2 == "area" and f1 in ("way", "area"):
+# if ":area" in tags:
+ return True
+# else:
+# return False
+ elif f2 == "line" and f1 in ("way", "line", "area"):
+ return True
+ else:
+ return False
+ # print f1, f2, True
+ return True
diff --git a/tools/kothic/mapcss/StyleChooser.py b/tools/kothic/mapcss/StyleChooser.py
new file mode 100644
index 0000000000..b2cfb723cc
--- /dev/null
+++ b/tools/kothic/mapcss/StyleChooser.py
@@ -0,0 +1,291 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# This file is part of kothic, the realtime map renderer.
+
+# kothic 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.
+
+# kothic 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 kothic. If not, see .
+
+
+from Rule import Rule
+from webcolors.webcolors import whatever_to_cairo as colorparser
+from webcolors.webcolors import cairo_to_hex
+from Eval import Eval
+from Condition import *
+
+TYPE_EVAL = type(Eval())
+
+def make_nice_style(r):
+ ra = {}
+ for a, b in r.iteritems():
+ "checking and nicifying style table"
+ if type(b) == TYPE_EVAL:
+ ra[a] = b
+ elif "color" in a:
+ "parsing color value to 3-tuple"
+ # print "res:", b
+ if b and (type(b) != tuple):
+ # if not b:
+ # print sl, ftype, tags, zoom, scale, zscale
+ # else:
+ ra[a] = colorparser(b)
+ elif b:
+ ra[a] = b
+ elif any(x in a for x in ("width", "z-index", "opacity", "offset", "radius", "extrude")):
+ "these things are float's or not in table at all"
+ try:
+ ra[a] = float(b)
+ except ValueError:
+ pass
+ elif "dashes" in a and type(b) != list:
+ "these things are arrays of float's or not in table at all"
+ try:
+ b = b.split(",")
+ b = [float(x) for x in b]
+ ra[a] = b
+ except ValueError:
+ ra[a] = []
+ else:
+ ra[a] = b
+ return ra
+
+
+class StyleChooser:
+ """
+ A StyleChooser object is equivalent to one CSS selector+declaration.
+
+ Its ruleChains property is an array of all the selectors, which would
+ traditionally be comma-separated. For example:
+ h1, h2, h3 em
+ is three ruleChains.
+
+ Each ruleChain is itself an array of nested selectors. So the above
+ example would roughly be encoded as:
+ [[h1],[h2],[h3,em]]
+ ^^ ^^ ^^ ^^ each of these is a Rule
+
+ The styles property is an array of all the style objects to be drawn
+ if any of the ruleChains evaluate to true.
+ """
+ def __repr__(self):
+ return "{(%s) : [%s] }\n" % (self.ruleChains, self.styles)
+
+ def __init__(self, scalepair):
+ self.ruleChains = []
+ self.styles = []
+ self.eval_type = TYPE_EVAL
+ self.scalepair = scalepair
+ self.selzooms = None
+ self.compatible_types = set()
+ self.has_evals = False
+
+ def get_numerics(self):
+ """
+ Returns a set of number-compared values.
+ """
+ a = set()
+ for r in self.ruleChains:
+ a.update(r.get_numerics())
+ a.discard(False)
+ return a
+
+ def get_interesting_tags(self, ztype, zoom):
+ """
+ Returns a set of tags that were used in here.
+ """
+ ### FIXME
+ a = set()
+ for r in self.ruleChains:
+ a.update(r.get_interesting_tags(ztype, zoom))
+ if a: # FIXME: semi-illegal optimization, may wreck in future on tagless matches
+ for r in self.styles:
+ for c, b in r.iteritems():
+ if type(b) == self.eval_type:
+ a.update(b.extract_tags())
+ return a
+
+ def extract_tags(self):
+ a = set()
+ for r in self.ruleChains:
+ a.update(r.extract_tags())
+ if "*" in a:
+ a.clear()
+ a.add("*")
+ break
+ if self.has_evals and "*" not in a:
+ for s in self.styles:
+ for v in s.values():
+ if type(v) == self.eval_type:
+ a.update(v.extract_tags())
+ if "*" in a or len(a) == 0:
+ a.clear()
+ a.add("*")
+ return a
+
+ def get_sql_hints(self, type, zoom):
+ """
+ Returns a set of tags that were used in here in form of SQL-hints.
+ """
+ a = set()
+ b = ""
+ needed = set(["width", "casing-width", "fill-color", "fill-image", "icon-image", "text", "extrude", "background-image", "background-color", "pattern-image", "shield-text"])
+
+ if not needed.isdisjoint(set(self.styles[0].keys())):
+ for r in self.ruleChains:
+ p = r.get_sql_hints(type, zoom)
+ if p:
+ q = "(" + p[1] + ")" # [t[1] for t in p]
+ if q == "()":
+ q = ""
+ if b and q:
+ b += " OR " + q
+ else:
+ b = q
+ a.update(p[0])
+ # no need to check for eval's
+ return a, b
+
+ def updateStyles(self, sl, ftype, tags, zoom, scale, zscale):
+ # Are any of the ruleChains fulfilled?
+ if self.selzooms:
+ if zoom < self.selzooms[0] or zoom > self.selzooms[1]:
+ return sl
+
+ #if ftype not in self.compatible_types:
+# return sl
+
+ object_id = self.testChain(self.ruleChains, ftype, tags, zoom)
+
+ if not object_id:
+ return sl
+
+ w = 0
+
+ for r in self.styles:
+ if self.has_evals:
+ ra = {}
+ for a, b in r.iteritems():
+ "calculating eval()'s"
+ if type(b) == self.eval_type:
+ combined_style = {}
+ for t in sl:
+ combined_style.update(t)
+ for p, q in combined_style.iteritems():
+ if "color" in p:
+ combined_style[p] = cairo_to_hex(q)
+ b = b.compute(tags, combined_style, scale, zscale)
+ ra[a] = b
+ #r = ra
+ ra = make_nice_style(ra)
+ else:
+ ra = r.copy()
+
+ ra["object-id"] = str(object_id)
+ hasall = False
+ allinit = {}
+ for x in sl:
+ if x.get("object-id") == "::*":
+ allinit = x.copy()
+ if ra["object-id"] == "::*":
+ oid = x.get("object-id")
+ x.update(ra)
+ x["object-id"] = oid
+ if oid == "::*":
+ hasall = True
+ else:
+ if x.get("object-id") == ra["object-id"]:
+ x.update(ra)
+ break
+ else:
+ if not hasall:
+ allinit.update(ra)
+ sl.append(allinit)
+
+ return sl
+
+ def testChain(self, chain, obj, tags, zoom):
+ """
+ Tests an object against a chain
+ """
+ for r in chain:
+ tt = r.test(obj, tags, zoom)
+ if tt:
+ return tt
+ return False
+
+ def newGroup(self):
+ """
+ starts a new ruleChain in this.ruleChains
+ """
+ pass
+
+ def newObject(self, e=''):
+ # print "newRule"
+ """
+ adds into the current ruleChain (starting a new Rule)
+ """
+ rule = Rule(e)
+ rule.minZoom = float(self.scalepair[0])
+ rule.maxZoom = float(self.scalepair[1])
+ self.ruleChains.append(rule)
+
+ def addZoom(self, z):
+ # print "addZoom ", float(z[0]), ", ", float(z[1])
+ """
+ adds into the current ruleChain (existing Rule)
+ """
+ self.ruleChains[-1].minZoom = float(z[0])
+ self.ruleChains[-1].maxZoom = float(z[1])
+
+ def addCondition(self, c):
+ # print "addCondition ", c
+ """
+ adds into the current ruleChain (existing Rule)
+ """
+ c = OptimizeCondition(c)
+ self.ruleChains[-1].conditions.append(c)
+
+ def addStyles(self, a):
+ # print "addStyle ", a
+ """
+ adds to this.styles
+ """
+ for r in self.ruleChains:
+ if not self.selzooms:
+ self.selzooms = [r.minZoom, r.maxZoom]
+ else:
+ self.selzooms[0] = min(self.selzooms[0], r.minZoom)
+ self.selzooms[1] = max(self.selzooms[1], r.maxZoom)
+ self.compatible_types.update(r.get_compatible_types())
+ rb = []
+ for r in a:
+ ra = {}
+ for a, b in r.iteritems():
+ a = a.strip()
+ b = b.strip()
+ if a == "casing-width":
+ "josm support"
+ if b[0] == "+":
+ try:
+ b = str(float(b) / 2)
+ except:
+ pass
+ if "text" == a[-4:]:
+ if b[:5] != "eval(":
+ b = "eval(tag(\"" + b + "\"))"
+ if b[:5] == "eval(":
+ b = Eval(b)
+ self.has_evals = True
+ ra[a] = b
+ ra = make_nice_style(ra)
+ rb.append(ra)
+ self.styles = self.styles + rb
diff --git a/tools/kothic/mapcss/__init__.py b/tools/kothic/mapcss/__init__.py
new file mode 100644
index 0000000000..427c9dba33
--- /dev/null
+++ b/tools/kothic/mapcss/__init__.py
@@ -0,0 +1,471 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# This file is part of kothic, the realtime map renderer.
+
+# kothic 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.
+
+# kothic 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 kothic. If not, see .
+
+import re
+import os
+import logging
+from hashlib import md5
+
+from copy import copy, deepcopy
+
+
+from StyleChooser import StyleChooser
+from Condition import Condition
+
+
+NEEDED_KEYS = set(["width", "casing-width", "fill-color", "fill-image", "icon-image", "text", "extrude",
+ "background-image", "background-color", "pattern-image", "shield-text", "symbol-shape"])
+
+
+WHITESPACE = re.compile(r'^ \s+ ', re.S | re.X)
+
+COMMENT = re.compile(r'^ \/\* .+? \*\/ \s* ', re.S | re.X)
+CLASS = re.compile(r'^ ([\.:]:?[*\w]+) \s* ', re.S | re.X)
+#NOT_CLASS = re.compile(r'^ !([\.:]\w+) \s* ', re.S | re.X)
+ZOOM = re.compile(r'^ \| \s* z([\d\-]+) \s* ', re.I | re.S | re.X)
+GROUP = re.compile(r'^ , \s* ', re.I | re.S | re.X)
+CONDITION = re.compile(r'^ \[(.+?)\] \s* ', re.S | re.X)
+OBJECT = re.compile(r'^ (\*|[\w]+) \s* ', re.S | re.X)
+DECLARATION = re.compile(r'^ \{(.+?)\} \s* ', re.S | re.X)
+IMPORT = re.compile(r'^@import\("(.+?)"\); \s* ', re.S | re.X)
+VARIABLE_SET = re.compile(r'^@([a-z][\w\d]*) \s* : \s* (.+?) \s* ; \s* ', re.S | re.X | re.I)
+UNKNOWN = re.compile(r'^ (\S+) \s* ', re.S | re.X)
+
+ZOOM_MINMAX = re.compile(r'^ (\d+)\-(\d+) $', re.S | re.X)
+ZOOM_MIN = re.compile(r'^ (\d+)\- $', re.S | re.X)
+ZOOM_MAX = re.compile(r'^ \-(\d+) $', re.S | re.X)
+ZOOM_SINGLE = re.compile(r'^ (\d+) $', re.S | re.X)
+
+CONDITION_TRUE = re.compile(r'^ \s* ([:\w]+) \s* [?] \s* $', re.I | re.S | re.X)
+CONDITION_invTRUE = re.compile(r'^ \s* [!] \s* ([:\w]+) \s* [?] \s* $', re.I | re.S | re.X)
+CONDITION_FALSE = re.compile(r'^ \s* ([:\w]+) \s* = \s* no \s* $', re.I | re.S | re.X)
+CONDITION_SET = re.compile(r'^ \s* ([-:\w]+) \s* $', re.S | re.X)
+CONDITION_UNSET = re.compile(r'^ \s* !([:\w]+) \s* $', re.S | re.X)
+CONDITION_EQ = re.compile(r'^ \s* ([:\w]+) \s* = \s* (.+) \s* $', re.S | re.X)
+CONDITION_NE = re.compile(r'^ \s* ([:\w]+) \s* != \s* (.+) \s* $', re.S | re.X)
+CONDITION_GT = re.compile(r'^ \s* ([:\w]+) \s* > \s* (.+) \s* $', re.S | re.X)
+CONDITION_GE = re.compile(r'^ \s* ([:\w]+) \s* >= \s* (.+) \s* $', re.S | re.X)
+CONDITION_LT = re.compile(r'^ \s* ([:\w]+) \s* < \s* (.+) \s* $', re.S | re.X)
+CONDITION_LE = re.compile(r'^ \s* ([:\w]+) \s* <= \s* (.+) \s* $', re.S | re.X)
+CONDITION_REGEX = re.compile(r'^ \s* ([:\w]+) \s* =~\/ \s* (.+) \/ \s* $', re.S | re.X)
+
+ASSIGNMENT_EVAL = re.compile(r"^ \s* (\S+) \s* \: \s* eval \s* \( \s* ' (.+?) ' \s* \) \s* $", re.I | re.S | re.X)
+ASSIGNMENT = re.compile(r'^ \s* (\S+) \s* \: \s* (.+?) \s* $', re.S | re.X)
+SET_TAG_EVAL = re.compile(r"^ \s* set \s+(\S+)\s* = \s* eval \s* \( \s* ' (.+?) ' \s* \) \s* $", re.I | re.S | re.X)
+SET_TAG = re.compile(r'^ \s* set \s+(\S+)\s* = \s* (.+?) \s* $', re.I | re.S | re.X)
+SET_TAG_TRUE = re.compile(r'^ \s* set \s+(\S+)\s* $', re.I | re.S | re.X)
+EXIT = re.compile(r'^ \s* exit \s* $', re.I | re.S | re.X)
+
+oZOOM = 2
+oGROUP = 3
+oCONDITION = 4
+oOBJECT = 5
+oDECLARATION = 6
+oSUBPART = 7
+oVARIABLE_SET = 8
+
+DASH = re.compile(r'\-/g')
+COLOR = re.compile(r'color$/')
+BOLD = re.compile(r'^bold$/i')
+ITALIC = re.compile(r'^italic|oblique$/i')
+UNDERLINE = re.compile(r'^underline$/i')
+CAPS = re.compile(r'^uppercase$/i')
+CENTER = re.compile(r'^center$/i')
+
+HEX = re.compile(r'^#([0-9a-f]+)$/i')
+VARIABLE = re.compile(r'@([a-z][\w\d]*)')
+
+
+class MapCSS():
+ def __init__(self, minscale=0, maxscale=19):
+ """
+ """
+ self.cache = {}
+ self.cache["style"] = {}
+ self.minscale = minscale
+ self.maxscale = maxscale
+ self.scalepair = (minscale, maxscale)
+ self.choosers = []
+ self.choosers_by_type = {}
+ self.choosers_by_type_and_tag = {}
+ self.variables = {}
+ self.style_loaded = False
+
+ def parseZoom(self, s):
+ if ZOOM_MINMAX.match(s):
+ return tuple([float(i) for i in ZOOM_MINMAX.match(s).groups()])
+ elif ZOOM_MIN.match(s):
+ return float(ZOOM_MIN.match(s).groups()[0]), self.maxscale
+ elif ZOOM_MAX.match(s):
+ return float(self.minscale), float(ZOOM_MAX.match(s).groups()[0])
+ elif ZOOM_SINGLE.match(s):
+ return float(ZOOM_SINGLE.match(s).groups()[0]), float(ZOOM_SINGLE.match(s).groups()[0])
+ else:
+ logging.error("unparsed zoom: %s" % s)
+
+ def build_choosers_tree(self, clname, type, tags={}):
+ if type not in self.choosers_by_type_and_tag:
+ self.choosers_by_type_and_tag[type] = {}
+ if clname not in self.choosers_by_type_and_tag[type]:
+ self.choosers_by_type_and_tag[type][clname] = set()
+ if type in self.choosers_by_type:
+ for chooser in self.choosers_by_type[type]:
+ for tag in chooser.extract_tags():
+ if tag == "*" or tag in tags:
+ if chooser not in self.choosers_by_type_and_tag[type][clname]:
+ self.choosers_by_type_and_tag[type][clname].add(chooser)
+ break
+
+ def restore_choosers_order(self, type):
+ ethalon_choosers = self.choosers_by_type[type]
+ for tag, choosers_for_tag in self.choosers_by_type_and_tag[type].items():
+ tmp = []
+ for ec in ethalon_choosers:
+ if ec in choosers_for_tag:
+ tmp.append(ec)
+ self.choosers_by_type_and_tag[type][tag] = tmp
+
+ def get_style(self, clname, type, tags={}, zoom=0, scale=1, zscale=.5, cache=True):
+ """
+ Kothic styling API
+ """
+ if cache:
+ shash = md5(repr(type) + repr(tags) + repr(zoom)).digest()
+ if shash in self.cache["style"]:
+ return deepcopy(self.cache["style"][shash])
+ style = []
+ if type in self.choosers_by_type_and_tag:
+ choosers = self.choosers_by_type_and_tag[type][clname]
+ for chooser in choosers:
+ style = chooser.updateStyles(style, type, tags, zoom, scale, zscale)
+ style = [x for x in style if x["object-id"] != "::*"]
+ for x in style:
+ for k, v in [('width', 0), ('casing-width', 0)]:
+ if k in x:
+ if x[k] == v:
+ del x[k]
+ st = []
+ for x in style:
+ if not NEEDED_KEYS.isdisjoint(x):
+ st.append(x)
+ style = st
+
+ if cache:
+ self.cache["style"][shash] = deepcopy(style)
+ return style
+
+ def get_style_dict(self, clname, type, tags={}, zoom=0, scale=1, zscale=.5, olddict={}, cache=True):
+ r = self.get_style(clname, type, tags, zoom, scale, zscale, cache)
+ d = olddict
+ for x in r:
+ if x.get('object-id', '') not in d:
+ d[x.get('object-id', '')] = {}
+ d[x.get('object-id', '')].update(x)
+ return d
+
+ def get_interesting_tags(self, type=None, zoom=None):
+ """
+ Get set of interesting tags.
+ """
+ tags = set()
+ for chooser in self.choosers:
+ tags.update(chooser.get_interesting_tags(type, zoom))
+ return tags
+
+ def get_sql_hints(self, type=None, zoom=None):
+ """
+ Get set of interesting tags.
+ """
+ hints = []
+ for chooser in self.choosers:
+ p = chooser.get_sql_hints(type, zoom)
+ if p:
+ if p[0] and p[1]:
+ hints.append(p)
+ return hints
+
+ def subst_variables(self, t):
+ """Expects an array from parseDeclaration."""
+ for k in t[0]:
+ t[0][k] = VARIABLE.sub(self.get_variable, t[0][k])
+ return t
+
+ def get_variable(self, m):
+ name = m.group()[1:]
+ if not name in self.variables:
+ raise Exception("Variable not found: " + str(format(name)))
+ return self.variables[name] if name in self.variables else m.group()
+
+ def parse(self, css=None, clamp=True, stretch=1000, filename=None):
+ """
+ Parses MapCSS given as string
+ """
+ basepath = os.curdir
+ if filename:
+ basepath = os.path.dirname(filename)
+ if not css:
+ css = open(filename).read()
+ if not self.style_loaded:
+ self.choosers = []
+
+ log = logging.getLogger('mapcss.parser')
+ previous = 0 # what was the previous CSS word?
+ sc = StyleChooser(self.scalepair) # currently being assembled
+
+ stck = [] # filename, original, remained
+ stck.append([filename, css, css])
+ try:
+ while (len(stck) > 0):
+ css = stck[-1][1].lstrip() # remained
+
+ wasBroken = False
+ while (css):
+ # Class - :motorway, :builtup, :hover
+ if CLASS.match(css):
+ if previous == oDECLARATION:
+ self.choosers.append(sc)
+ sc = StyleChooser(self.scalepair)
+ cond = CLASS.match(css).groups()[0]
+ log.debug("class found: %s" % (cond))
+ css = CLASS.sub("", css)
+ sc.addCondition(Condition('eq', ("::class", cond)))
+ previous = oCONDITION
+
+ ## Not class - !.motorway, !.builtup, !:hover
+ #elif NOT_CLASS.match(css):
+ #if (previous == oDECLARATION):
+ #self.choosers.append(sc)
+ #sc = StyleChooser(self.scalepair)
+ #cond = NOT_CLASS.match(css).groups()[0]
+ #log.debug("not_class found: %s" % (cond))
+ #css = NOT_CLASS.sub("", css)
+ #sc.addCondition(Condition('ne', ("::class", cond)))
+ #previous = oCONDITION
+
+ # Zoom
+ elif ZOOM.match(css):
+ if (previous != oOBJECT & previous != oCONDITION):
+ sc.newObject()
+ cond = ZOOM.match(css).groups()[0]
+ log.debug("zoom found: %s" % (cond))
+ css = ZOOM.sub("", css)
+ sc.addZoom(self.parseZoom(cond))
+ previous = oZOOM
+
+ # Grouping - just a comma
+ elif GROUP.match(css):
+ css = GROUP.sub("", css)
+ sc.newGroup()
+ previous = oGROUP
+
+ # Condition - [highway=primary]
+ elif CONDITION.match(css):
+ if (previous == oDECLARATION):
+ self.choosers.append(sc)
+ sc = StyleChooser(self.scalepair)
+ if (previous != oOBJECT) and (previous != oZOOM) and (previous != oCONDITION):
+ sc.newObject()
+ cond = CONDITION.match(css).groups()[0]
+ log.debug("condition found: %s" % (cond))
+ css = CONDITION.sub("", css)
+ sc.addCondition(parseCondition(cond))
+ previous = oCONDITION
+
+ # Object - way, node, relation
+ elif OBJECT.match(css):
+ if (previous == oDECLARATION):
+ self.choosers.append(sc)
+ sc = StyleChooser(self.scalepair)
+ obj = OBJECT.match(css).groups()[0]
+ log.debug("object found: %s" % (obj))
+ css = OBJECT.sub("", css)
+ sc.newObject(obj)
+ previous = oOBJECT
+
+ # Declaration - {...}
+ elif DECLARATION.match(css):
+ decl = DECLARATION.match(css).groups()[0]
+ log.debug("declaration found: %s" % (decl))
+ sc.addStyles(self.subst_variables(parseDeclaration(decl)))
+ css = DECLARATION.sub("", css)
+ previous = oDECLARATION
+
+ # CSS comment
+ elif COMMENT.match(css):
+ log.debug("comment found")
+ css = COMMENT.sub("", css)
+
+ # @import("filename.css");
+ elif IMPORT.match(css):
+ log.debug("import found")
+ import_filename = os.path.join(basepath, IMPORT.match(css).groups()[0])
+ try:
+ css = IMPORT.sub("", css)
+ import_text = open(import_filename, "r").read()
+ stck[-1][1] = css # store remained part
+ stck.append([import_filename, import_text, import_text])
+ wasBroken = True
+ break
+ except IOError as e:
+ raise Exception("Cannot import file " + import_filename + "\n" + str(e))
+
+ # Variables
+ elif VARIABLE_SET.match(css):
+ name = VARIABLE_SET.match(css).groups()[0]
+ log.debug("variable set found: %s" % name)
+ self.variables[name] = VARIABLE_SET.match(css).groups()[1]
+ css = VARIABLE_SET.sub("", css)
+ previous = oVARIABLE_SET
+
+ # Unknown pattern
+ elif UNKNOWN.match(css):
+ raise Exception("Unknown construction: " + UNKNOWN.match(css).group())
+
+ # Must be unreacheable
+ else:
+ raise Exception("Unexpected construction: " + css)
+
+ if not wasBroken:
+ stck.pop()
+
+ if (previous == oDECLARATION):
+ self.choosers.append(sc)
+ sc = StyleChooser(self.scalepair)
+
+ except Exception as e:
+ filename = stck[-1][0] # filename
+ css_orig = stck[-1][2] # original
+ css = stck[-1][1] # remained
+ line = unicode(css_orig[:-len(unicode(css))]).count("\n") + 1
+ msg = str(e) + "\nFile: " + filename + "\nLine: " + str(line)
+ raise Exception(msg)
+
+ try:
+ if clamp:
+ "clamp z-indexes, so they're tightly following integers"
+ zindex = set()
+ for chooser in self.choosers:
+ for stylez in chooser.styles:
+ zindex.add(float(stylez.get('z-index', 0)))
+ zindex = list(zindex)
+ zindex.sort()
+ zoffset = len([x for x in zindex if x < 0])
+ for chooser in self.choosers:
+ for stylez in chooser.styles:
+ if 'z-index' in stylez:
+ res = zindex.index(float(stylez.get('z-index', 0))) - zoffset
+ if stretch:
+ stylez['z-index'] = 1. * res / len(zindex) * stretch
+ else:
+ stylez['z-index'] = res
+ except TypeError:
+ pass
+
+ for chooser in self.choosers:
+ for t in chooser.compatible_types:
+ if t not in self.choosers_by_type:
+ self.choosers_by_type[t] = [chooser]
+ else:
+ self.choosers_by_type[t].append(chooser)
+
+
+def parseCondition(s):
+ log = logging.getLogger('mapcss.parser.condition')
+
+ if CONDITION_TRUE.match(s):
+ a = CONDITION_TRUE.match(s).groups()
+ log.debug("condition true: %s" % (a[0]))
+ return Condition('true', a)
+
+ if CONDITION_invTRUE.match(s):
+ a = CONDITION_invTRUE.match(s).groups()
+ log.debug("condition invtrue: %s" % (a[0]))
+ return Condition('ne', (a[0], "yes"))
+
+ if CONDITION_FALSE.match(s):
+ a = CONDITION_FALSE.match(s).groups()
+ log.debug("condition false: %s" % (a[0]))
+ return Condition('false', a)
+
+ if CONDITION_SET.match(s):
+ a = CONDITION_SET.match(s).groups()
+ log.debug("condition set: %s" % (a))
+ return Condition('set', a)
+
+ if CONDITION_UNSET.match(s):
+ a = CONDITION_UNSET.match(s).groups()
+ log.debug("condition unset: %s" % (a))
+ return Condition('unset', a)
+
+ if CONDITION_NE.match(s):
+ a = CONDITION_NE.match(s).groups()
+ log.debug("condition NE: %s = %s" % (a[0], a[1]))
+ return Condition('ne', a)
+
+ if CONDITION_LE.match(s):
+ a = CONDITION_LE.match(s).groups()
+ log.debug("condition LE: %s <= %s" % (a[0], a[1]))
+ return Condition('<=', a)
+
+ if CONDITION_GE.match(s):
+ a = CONDITION_GE.match(s).groups()
+ log.debug("condition GE: %s >= %s" % (a[0], a[1]))
+ return Condition('>=', a)
+
+ if CONDITION_LT.match(s):
+ a = CONDITION_LT.match(s).groups()
+ log.debug("condition LT: %s < %s" % (a[0], a[1]))
+ return Condition('<', a)
+
+ if CONDITION_GT.match(s):
+ a = CONDITION_GT.match(s).groups()
+ log.debug("condition GT: %s > %s" % (a[0], a[1]))
+ return Condition('>', a)
+
+ if CONDITION_REGEX.match(s):
+ a = CONDITION_REGEX.match(s).groups()
+ log.debug("condition REGEX: %s = %s" % (a[0], a[1]))
+ return Condition('regex', a)
+
+ if CONDITION_EQ.match(s):
+ a = CONDITION_EQ.match(s).groups()
+ log.debug("condition EQ: %s = %s" % (a[0], a[1]))
+ return Condition('eq', a)
+
+ else:
+ raise Exception("condition UNKNOWN: " + s)
+
+
+def parseDeclaration(s):
+ """
+ Parse declaration string into list of styles
+ """
+ t = {}
+ for a in s.split(';'):
+ # if ((o=ASSIGNMENT_EVAL.exec(a))) { t[o[1].replace(DASH,'_')]=new Eval(o[2]); }
+ if ASSIGNMENT.match(a):
+ tzz = ASSIGNMENT.match(a).groups()
+ t[tzz[0]] = tzz[1].strip().strip('"')
+ logging.debug("%s == %s" % (tzz[0], tzz[1]))
+ else:
+ logging.debug("unknown %s" % (a))
+ return [t]
+
+
+if __name__ == "__main__":
+ logging.basicConfig(level=logging.WARNING)
+ mc = MapCSS(0, 19)
diff --git a/tools/kothic/mapcss/webcolors/LICENSE.txt b/tools/kothic/mapcss/webcolors/LICENSE.txt
new file mode 100644
index 0000000000..61deabe362
--- /dev/null
+++ b/tools/kothic/mapcss/webcolors/LICENSE.txt
@@ -0,0 +1,28 @@
+Copyright (c) 2008-2009, James Bennett
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of the author nor the names of other
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/tools/kothic/mapcss/webcolors/PKG-INFO b/tools/kothic/mapcss/webcolors/PKG-INFO
new file mode 100644
index 0000000000..baab7fc15c
--- /dev/null
+++ b/tools/kothic/mapcss/webcolors/PKG-INFO
@@ -0,0 +1,439 @@
+Metadata-Version: 1.0
+Name: webcolors
+Version: 1.3
+Summary: A library for working with sRGB color specifications as used in HTML and CSS.
+Home-page: http://www.bitbucket.org/ubernostrum/webcolors/overview/
+Author: James Bennett
+Author-email: james@b-list.org
+License: UNKNOWN
+Download-URL: http://bitbucket.org/ubernostrum/webcolors/downloads/webcolors-1.3.tar.gz
+Description: Web colors
+ ==========
+
+ A simple library for working with the color names and color codes
+ defined by the HTML and CSS specifications.
+
+
+ An overview of HTML and CSS colors
+ ----------------------------------
+
+ Colors on the Web are specified in `the sRGB color space`_, where each
+ color is made up of a red component, a green component and a blue
+ component. This is useful because it maps (fairly) cleanly to the red,
+ green and blue components of pixels on a computer display, and to the
+ cone cells of a human eye, which come in three sets roughly
+ corresponding to the wavelengths of light associated with red, green
+ and blue.
+
+ `The HTML 4 standard`_ defines two ways to specify sRGB colors:
+
+ * A hash mark ('#') followed by three pairs of hexdecimal digits,
+ specifying values for red, green and blue components in that order;
+ for example, ``#0099cc``. Since each pair of hexadecimal digits can
+ express 256 different values, this allows up to 256**3 or 16,777,216
+ unique colors to be specified (though, due to differences in display
+ technology, not all of these colors may be clearly distinguished on
+ any given physical display).
+
+ * A set of predefined color names which correspond to specific
+ hexadecimal values; for example, ``white``. HTML 4 defines sixteen
+ such colors.
+
+ `The CSS 2 standard`_ allows any valid HTML 4 color specification, and
+ adds three new ways to specify sRGB colors:
+
+ * A hash mark followed by three hexadecimal digits, which is expanded
+ into three hexadecimal pairs by repeating each digit; thus ``#09c``
+ is equivalent to ``#0099cc``.
+
+ * The string 'rgb', followed by parentheses, between which are three
+ numeric values each between 0 and 255, inclusive, which are taken to
+ be the values of the red, green and blue components in that order;
+ for example, ``rgb(0, 153, 204)``.
+
+ * The same as above, except using percentages instead of numeric
+ values; for example, ``rgb(0%, 60%, 80%)``.
+
+ `The CSS 2.1 revision`_ does not add any new methods of specifying
+ sRGB colors, but does add one additional named color.
+
+ `The CSS 3 color module`_ (currently a W3C Candidate Recommendation)
+ adds one new way to specify sRGB colors:
+
+ * A hue-saturation-lightness triple (HSL), using the construct
+ ``hsl()``.
+
+ It also adds support for variable opacity of colors, by allowing the
+ specification of alpha-channel information, through the ``rgba()`` and
+ ``hsla()`` constructs, which are identical to ``rgb()`` and ``hsl()``
+ with one exception: a fourth value is supplied, indicating the level
+ of opacity from ``0.0`` (completely transparent) to ``1.0``
+ (completely opaque). Though not technically a color, the keyword
+ ``transparent`` is also made available in lieu of a color value, and
+ corresponds to ``rgba(0,0,0,0)``.
+
+ Additionally, CSS3 defines a new set of color names; this set is taken
+ directly from the named colors defined for SVG (Scalable Vector
+ Graphics) markup, and is a proper superset of the named colors defined
+ in CSS 2.1. This set also has significant overlap with traditional X11
+ color sets as defined by the ``rgb.txt`` file on many Unix and
+ Unix-like operating systems, though the correspondence is not exact;
+ the set of X11 colors is not standardized, and the set of CSS3 colors
+ contains some definitions which diverge significantly from customary
+ X11 definitions (for example, CSS3's ``green`` is not equivalent to
+ X11's ``green``; the value which X11 designates ``green`` is
+ designated ``lime`` in CSS3).
+
+ .. _the sRGB color space: http://www.w3.org/Graphics/Color/sRGB
+ .. _The HTML 4 standard: http://www.w3.org/TR/html401/types.html#h-6.5
+ .. _The CSS 2 standard: http://www.w3.org/TR/REC-CSS2/syndata.html#value-def-color
+ .. _The CSS 2.1 revision: http://www.w3.org/TR/CSS21/
+ .. _The CSS 3 color module: http://www.w3.org/TR/css3-color/
+
+ What this module supports
+ -------------------------
+
+ The mappings and functions within this module support the following
+ methods of specifying sRGB colors, and conversions between them:
+
+ * Six-digit hexadecimal.
+
+ * Three-digit hexadecimal.
+
+ * Integer ``rgb()`` triplet.
+
+ * Percentage ``rgb()`` triplet.
+
+ * Varying selections of predefined color names (see below).
+
+ This module does not support ``hsl()`` triplets, nor does it support
+ opacity/alpha-channel information via ``rgba()`` or ``hsla()``.
+
+ If you need to convert between RGB-specified colors and HSL-specified
+ colors, or colors specified via other means, consult `the colorsys
+ module`_ in the Python standard library, which can perform conversions
+ amongst several common color spaces.
+
+ .. _the colorsys module: http://docs.python.org/library/colorsys.html
+
+ Normalization
+ -------------
+
+ For colors specified via hexadecimal values, this module will accept
+ input in the following formats:
+
+ * A hash mark (#) followed by three hexadecimal digits, where letters
+ may be upper- or lower-case.
+
+ * A hash mark (#) followed by six hexadecimal digits, where letters
+ may be upper- or lower-case.
+
+ For output which consists of a color specified via hexadecimal values,
+ and for functions which perform intermediate conversion to hexadecimal
+ before returning a result in another format, this module always
+ normalizes such values to the following format:
+
+ * A hash mark (#) followed by six hexadecimal digits, with letters
+ forced to lower-case.
+
+ The function ``normalize_hex()`` in this module can be used to perform
+ this normalization manually if desired; see its documentation for an
+ explanation of the normalization process.
+
+ For colors specified via predefined names, this module will accept
+ input in the following formats:
+
+ * An entirely lower-case name, such as ``aliceblue``.
+
+ * A name using initial capitals, such as ``AliceBlue``.
+
+ For output which consists of a color specified via a predefined name,
+ and for functions which perform intermediate conversion to a
+ predefined name before returning a result in another format, this
+ module always normalizes such values to be entirely lower-case.
+
+ Mappings of color names
+ -----------------------
+
+ For each set of defined color names -- HTML 4, CSS 2, CSS 2.1 and CSS
+ 3 -- this module exports two mappings: one of normalized color names
+ to normalized hexadecimal values, and one of normalized hexadecimal
+ values to normalized color names. These eight mappings are as follows:
+
+ ``html4_names_to_hex``
+ Mapping of normalized HTML 4 color names to normalized hexadecimal
+ values.
+
+ ``html4_hex_to_names``
+ Mapping of normalized hexadecimal values to normalized HTML 4
+ color names.
+
+ ``css2_names_to_hex``
+ Mapping of normalized CSS 2 color names to normalized hexadecimal
+ values. Because CSS 2 defines the same set of named colors as HTML
+ 4, this is merely an alias for ``html4_names_to_hex``.
+
+ ``css2_hex_to_names``
+ Mapping of normalized hexadecimal values to normalized CSS 2 color
+ nams. For the reasons described above, this is merely an alias for
+ ``html4_hex_to_names``.
+
+ ``css21_names_to_hex``
+ Mapping of normalized CSS 2.1 color names to normalized
+ hexadecimal values. This is identical to ``html4_names_to_hex``,
+ except for one addition: ``orange``.
+
+ ``css21_hex_to_names``
+ Mapping of normalized hexadecimal values to normalized CSS 2.1
+ color names. As above, this is identical to ``html4_hex_to_names``
+ except for the addition of ``orange``.
+
+ ``css3_names_to_hex``
+ Mapping of normalized CSS3 color names to normalized hexadecimal
+ values.
+
+ ``css3_hex_to_names``
+ Mapping of normalized hexadecimal values to normalized CSS3 color
+ names.
+
+ Normalization functions
+ -----------------------
+
+ ``normalize_hex(hex_value)``
+ Normalize a hexadecimal color value to the following form and
+ return the result::
+
+ #[a-f0-9]{6}
+
+ In other words, the following transformations are applied as
+ needed:
+
+ * If the value contains only three hexadecimal digits, it is
+ expanded to six.
+
+ * The value is normalized to lower-case.
+
+ If the supplied value cannot be interpreted as a hexadecimal color
+ value, ``ValueError`` is raised.
+
+ Example:
+
+ >>> normalize_hex('#09c')
+ '#0099cc'
+
+ Conversions from named colors
+ -----------------------------
+
+ ``name_to_hex(name, spec='css3')``
+ Convert a color name to a normalized hexadecimal color value.
+
+ The optional keyword argument ``spec`` determines which
+ specification's list of color names will be used; valid values are
+ ``html4``, ``css2``, ``css21`` and ``css3``, and the default is
+ ``css3``.
+
+ The color name will be normalized to lower-case before being
+ looked up, and when no color of that name exists in the given
+ specification, ``ValueError`` is raised.
+
+ Example:
+
+ >>> name_to_hex('deepskyblue')
+ '#00bfff'
+
+ ``name_to_rgb(name, spec='css3')``
+ Convert a color name to a 3-tuple of integers suitable for use in
+ an ``rgb()`` triplet specifying that color.
+
+ The optional keyword argument ``spec`` determines which
+ specification's list of color names will be used; valid values are
+ ``html4``, ``css2``, ``css21`` and ``css3``, and the default is
+ ``css3``.
+
+ The color name will be normalized to lower-case before being
+ looked up, and when no color of that name exists in the given
+ specification, ``ValueError`` is raised.
+
+ Example:
+
+ >>> name_to_rgb('navy')
+ (0, 0, 128)
+
+ ``name_to_rgb_percent(name, spec='css3')``
+ Convert a color name to a 3-tuple of percentages suitable for use
+ in an ``rgb()`` triplet specifying that color.
+
+ The optional keyword argument ``spec`` determines which
+ specification's list of color names will be used; valid values are
+ ``html4``, ``css2``, ``css21`` and ``css3``, and the default is
+ ``css3``.
+
+ The color name will be normalized to lower-case before being
+ looked up, and when no color of that name exists in the given
+ specification, ``ValueError`` is raised.
+
+ Example:
+
+ >>> name_to_rgb_percent('navy')
+ ('0%', '0%', '50%')
+
+
+ Conversions from hexadecimal values
+ -----------------------------------
+
+ ``hex_to_name(hex_value, spec='css3')``
+ Convert a hexadecimal color value to its corresponding normalized
+ color name, if any such name exists.
+
+ The optional keyword argument ``spec`` determines which
+ specification's list of color names will be used; valid values are
+ ``html4``, ``css2``, ``css21`` and ``css3``, and the default is
+ ``css3``.
+
+ The hexadecimal value will be normalized before being looked up,
+ and when no color name for the value is found in the given
+ specification, ``ValueError`` is raised.
+
+ Example:
+
+ >>> hex_to_name('#000080')
+ 'navy'
+
+ ``hex_to_rgb(hex_value)``
+ Convert a hexadecimal color value to a 3-tuple of integers
+ suitable for use in an ``rgb()`` triplet specifying that color.
+
+ The hexadecimal value will be normalized before being converted.
+
+ Example:
+
+ >>> hex_to_rgb('#000080')
+ (0, 0, 128)
+
+ ``hex_to_rgb_percent(hex_value)``
+ Convert a hexadecimal color value to a 3-tuple of percentages
+ suitable for use in an ``rgb()`` triplet representing that color.
+
+ The hexadecimal value will be normalized before converting.
+
+ Example:
+
+ >>> hex_to_rgb_percent('#ffffff')
+ ('100%', '100%', '100%')
+
+
+ Conversions from integer rgb() triplets
+ ---------------------------------------
+
+ ``rgb_to_name(rgb_triplet, spec='css3')``
+ Convert a 3-tuple of integers, suitable for use in an ``rgb()``
+ color triplet, to its corresponding normalized color name, if any
+ such name exists.
+
+ The optional keyword argument ``spec`` determines which
+ specification's list of color names will be used; valid values are
+ ``html4``, ``css2``, ``css21`` and ``css3``, and the default is
+ ``css3``.
+
+ If there is no matching name, ``ValueError`` is raised.
+
+ Example:
+
+ >>> rgb_to_name((0, 0, 0))
+ 'black'
+
+ ``rgb_to_hex(rgb_triplet)``
+ Convert a 3-tuple of integers, suitable for use in an ``rgb()``
+ color triplet, to a normalized hexadecimal value for that color.
+
+ Example:
+
+ >>> rgb_to_hex((255, 255, 255))
+ '#ffffff'
+
+ ``rgb_to_rgb_percent(rgb_triplet)``
+ Convert a 3-tuple of integers, suitable for use in an ``rgb()``
+ color triplet, to a 3-tuple of percentages suitable for use in
+ representing that color.
+
+ This function makes some trade-offs in terms of the accuracy of
+ the final representation; for some common integer values,
+ special-case logic is used to ensure a precise result (e.g.,
+ integer 128 will always convert to '50%', integer 32 will always
+ convert to '12.5%'), but for all other values a standard Python
+ ``float`` is used and rounded to two decimal places, which may
+ result in a loss of precision for some values.
+
+ Examples:
+
+ >>> rgb_to_rgb_percent((255, 255, 255))
+ ('100%', '100%', '100%')
+ >>> rgb_to_rgb_percent((0, 0, 128))
+ ('0%', '0%', '50%')
+ >>> rgb_to_rgb_percent((33, 56, 192))
+ ('12.94%', '21.96%', '75.29%')
+ >>> rgb_to_rgb_percent((64, 32, 16))
+ ('25%', '12.5%', '6.25%')
+
+
+ Conversions from percentage rgb() triplets
+ ------------------------------------------
+
+ ``rgb_percent_to_name(rgb_percent_triplet, spec='css3')``
+ Convert a 3-tuple of percentages, suitable for use in an ``rgb()``
+ color triplet, to its corresponding normalized color name, if any
+ such name exists.
+
+ The optional keyword argument ``spec`` determines which
+ specification's list of color names will be used; valid values are
+ ``html4``, ``css2``, ``css21`` and ``css3``, and the default is
+ ``css3``.
+
+ If there is no matching name, ``ValueError`` is raised.
+
+ Example:
+
+ >>> rgb_percent_to_name(('0%', '0%', '50%'))
+ 'navy'
+
+ ``rgb_percent_to_hex(rgb_percent_triplet)``
+ Convert a 3-tuple of percentages, suitable for use in an ``rgb()``
+ color triplet, to a normalized hexadecimal color value for that
+ color.
+
+ Example:
+
+ >>> rgb_percent_to_hex(('100%', '100%', '0%'))
+ '#ffff00'
+
+ ``rgb_percent_to_rgb(rgb_percent_triplet)``
+ Convert a 3-tuple of percentages, suitable for use in an ``rgb()``
+ color triplet, to a 3-tuple of integers suitable for use in
+ representing that color.
+
+ Some precision may be lost in this conversion. See the note
+ regarding precision for ``rgb_to_rgb_percent()`` for details;
+ generally speaking, the following is true for any 3-tuple ``t`` of
+ integers in the range 0...255 inclusive::
+
+ t == rgb_percent_to_rgb(rgb_to_rgb_percent(t))
+
+ Examples:
+
+ >>> rgb_percent_to_rgb(('100%', '100%', '100%'))
+ (255, 255, 255)
+ >>> rgb_percent_to_rgb(('0%', '0%', '50%'))
+ (0, 0, 128)
+ >>> rgb_percent_to_rgb(('25%', '12.5%', '6.25%'))
+ (64, 32, 16)
+ >>> rgb_percent_to_rgb(('12.94%', '21.96%', '75.29%'))
+ (33, 56, 192)
+
+Platform: UNKNOWN
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Environment :: Web Environment
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: BSD License
+Classifier: Operating System :: OS Independent
+Classifier: Programming Language :: Python
+Classifier: Topic :: Utilities
diff --git a/tools/kothic/mapcss/webcolors/README.txt b/tools/kothic/mapcss/webcolors/README.txt
new file mode 100644
index 0000000000..4d95ae8cd6
--- /dev/null
+++ b/tools/kothic/mapcss/webcolors/README.txt
@@ -0,0 +1,421 @@
+Web colors
+==========
+
+A simple library for working with the color names and color codes
+defined by the HTML and CSS specifications.
+
+
+An overview of HTML and CSS colors
+----------------------------------
+
+Colors on the Web are specified in `the sRGB color space`_, where each
+color is made up of a red component, a green component and a blue
+component. This is useful because it maps (fairly) cleanly to the red,
+green and blue components of pixels on a computer display, and to the
+cone cells of a human eye, which come in three sets roughly
+corresponding to the wavelengths of light associated with red, green
+and blue.
+
+`The HTML 4 standard`_ defines two ways to specify sRGB colors:
+
+* A hash mark ('#') followed by three pairs of hexdecimal digits,
+ specifying values for red, green and blue components in that order;
+ for example, ``#0099cc``. Since each pair of hexadecimal digits can
+ express 256 different values, this allows up to 256**3 or 16,777,216
+ unique colors to be specified (though, due to differences in display
+ technology, not all of these colors may be clearly distinguished on
+ any given physical display).
+
+* A set of predefined color names which correspond to specific
+ hexadecimal values; for example, ``white``. HTML 4 defines sixteen
+ such colors.
+
+`The CSS 2 standard`_ allows any valid HTML 4 color specification, and
+adds three new ways to specify sRGB colors:
+
+* A hash mark followed by three hexadecimal digits, which is expanded
+ into three hexadecimal pairs by repeating each digit; thus ``#09c``
+ is equivalent to ``#0099cc``.
+
+* The string 'rgb', followed by parentheses, between which are three
+ numeric values each between 0 and 255, inclusive, which are taken to
+ be the values of the red, green and blue components in that order;
+ for example, ``rgb(0, 153, 204)``.
+
+* The same as above, except using percentages instead of numeric
+ values; for example, ``rgb(0%, 60%, 80%)``.
+
+`The CSS 2.1 revision`_ does not add any new methods of specifying
+sRGB colors, but does add one additional named color.
+
+`The CSS 3 color module`_ (currently a W3C Candidate Recommendation)
+adds one new way to specify sRGB colors:
+
+* A hue-saturation-lightness triple (HSL), using the construct
+ ``hsl()``.
+
+It also adds support for variable opacity of colors, by allowing the
+specification of alpha-channel information, through the ``rgba()`` and
+``hsla()`` constructs, which are identical to ``rgb()`` and ``hsl()``
+with one exception: a fourth value is supplied, indicating the level
+of opacity from ``0.0`` (completely transparent) to ``1.0``
+(completely opaque). Though not technically a color, the keyword
+``transparent`` is also made available in lieu of a color value, and
+corresponds to ``rgba(0,0,0,0)``.
+
+Additionally, CSS3 defines a new set of color names; this set is taken
+directly from the named colors defined for SVG (Scalable Vector
+Graphics) markup, and is a proper superset of the named colors defined
+in CSS 2.1. This set also has significant overlap with traditional X11
+color sets as defined by the ``rgb.txt`` file on many Unix and
+Unix-like operating systems, though the correspondence is not exact;
+the set of X11 colors is not standardized, and the set of CSS3 colors
+contains some definitions which diverge significantly from customary
+X11 definitions (for example, CSS3's ``green`` is not equivalent to
+X11's ``green``; the value which X11 designates ``green`` is
+designated ``lime`` in CSS3).
+
+.. _the sRGB color space: http://www.w3.org/Graphics/Color/sRGB
+.. _The HTML 4 standard: http://www.w3.org/TR/html401/types.html#h-6.5
+.. _The CSS 2 standard: http://www.w3.org/TR/REC-CSS2/syndata.html#value-def-color
+.. _The CSS 2.1 revision: http://www.w3.org/TR/CSS21/
+.. _The CSS 3 color module: http://www.w3.org/TR/css3-color/
+
+What this module supports
+-------------------------
+
+The mappings and functions within this module support the following
+methods of specifying sRGB colors, and conversions between them:
+
+* Six-digit hexadecimal.
+
+* Three-digit hexadecimal.
+
+* Integer ``rgb()`` triplet.
+
+* Percentage ``rgb()`` triplet.
+
+* Varying selections of predefined color names (see below).
+
+This module does not support ``hsl()`` triplets, nor does it support
+opacity/alpha-channel information via ``rgba()`` or ``hsla()``.
+
+If you need to convert between RGB-specified colors and HSL-specified
+colors, or colors specified via other means, consult `the colorsys
+module`_ in the Python standard library, which can perform conversions
+amongst several common color spaces.
+
+.. _the colorsys module: http://docs.python.org/library/colorsys.html
+
+Normalization
+-------------
+
+For colors specified via hexadecimal values, this module will accept
+input in the following formats:
+
+* A hash mark (#) followed by three hexadecimal digits, where letters
+ may be upper- or lower-case.
+
+* A hash mark (#) followed by six hexadecimal digits, where letters
+ may be upper- or lower-case.
+
+For output which consists of a color specified via hexadecimal values,
+and for functions which perform intermediate conversion to hexadecimal
+before returning a result in another format, this module always
+normalizes such values to the following format:
+
+* A hash mark (#) followed by six hexadecimal digits, with letters
+ forced to lower-case.
+
+The function ``normalize_hex()`` in this module can be used to perform
+this normalization manually if desired; see its documentation for an
+explanation of the normalization process.
+
+For colors specified via predefined names, this module will accept
+input in the following formats:
+
+* An entirely lower-case name, such as ``aliceblue``.
+
+* A name using initial capitals, such as ``AliceBlue``.
+
+For output which consists of a color specified via a predefined name,
+and for functions which perform intermediate conversion to a
+predefined name before returning a result in another format, this
+module always normalizes such values to be entirely lower-case.
+
+Mappings of color names
+-----------------------
+
+For each set of defined color names -- HTML 4, CSS 2, CSS 2.1 and CSS
+3 -- this module exports two mappings: one of normalized color names
+to normalized hexadecimal values, and one of normalized hexadecimal
+values to normalized color names. These eight mappings are as follows:
+
+``html4_names_to_hex``
+ Mapping of normalized HTML 4 color names to normalized hexadecimal
+ values.
+
+``html4_hex_to_names``
+ Mapping of normalized hexadecimal values to normalized HTML 4
+ color names.
+
+``css2_names_to_hex``
+ Mapping of normalized CSS 2 color names to normalized hexadecimal
+ values. Because CSS 2 defines the same set of named colors as HTML
+ 4, this is merely an alias for ``html4_names_to_hex``.
+
+``css2_hex_to_names``
+ Mapping of normalized hexadecimal values to normalized CSS 2 color
+ nams. For the reasons described above, this is merely an alias for
+ ``html4_hex_to_names``.
+
+``css21_names_to_hex``
+ Mapping of normalized CSS 2.1 color names to normalized
+ hexadecimal values. This is identical to ``html4_names_to_hex``,
+ except for one addition: ``orange``.
+
+``css21_hex_to_names``
+ Mapping of normalized hexadecimal values to normalized CSS 2.1
+ color names. As above, this is identical to ``html4_hex_to_names``
+ except for the addition of ``orange``.
+
+``css3_names_to_hex``
+ Mapping of normalized CSS3 color names to normalized hexadecimal
+ values.
+
+``css3_hex_to_names``
+ Mapping of normalized hexadecimal values to normalized CSS3 color
+ names.
+
+Normalization functions
+-----------------------
+
+``normalize_hex(hex_value)``
+ Normalize a hexadecimal color value to the following form and
+ return the result::
+
+ #[a-f0-9]{6}
+
+ In other words, the following transformations are applied as
+ needed:
+
+ * If the value contains only three hexadecimal digits, it is
+ expanded to six.
+
+ * The value is normalized to lower-case.
+
+ If the supplied value cannot be interpreted as a hexadecimal color
+ value, ``ValueError`` is raised.
+
+ Example:
+
+ >>> normalize_hex('#09c')
+ '#0099cc'
+
+Conversions from named colors
+-----------------------------
+
+``name_to_hex(name, spec='css3')``
+ Convert a color name to a normalized hexadecimal color value.
+
+ The optional keyword argument ``spec`` determines which
+ specification's list of color names will be used; valid values are
+ ``html4``, ``css2``, ``css21`` and ``css3``, and the default is
+ ``css3``.
+
+ The color name will be normalized to lower-case before being
+ looked up, and when no color of that name exists in the given
+ specification, ``ValueError`` is raised.
+
+ Example:
+
+ >>> name_to_hex('deepskyblue')
+ '#00bfff'
+
+``name_to_rgb(name, spec='css3')``
+ Convert a color name to a 3-tuple of integers suitable for use in
+ an ``rgb()`` triplet specifying that color.
+
+ The optional keyword argument ``spec`` determines which
+ specification's list of color names will be used; valid values are
+ ``html4``, ``css2``, ``css21`` and ``css3``, and the default is
+ ``css3``.
+
+ The color name will be normalized to lower-case before being
+ looked up, and when no color of that name exists in the given
+ specification, ``ValueError`` is raised.
+
+ Example:
+
+ >>> name_to_rgb('navy')
+ (0, 0, 128)
+
+``name_to_rgb_percent(name, spec='css3')``
+ Convert a color name to a 3-tuple of percentages suitable for use
+ in an ``rgb()`` triplet specifying that color.
+
+ The optional keyword argument ``spec`` determines which
+ specification's list of color names will be used; valid values are
+ ``html4``, ``css2``, ``css21`` and ``css3``, and the default is
+ ``css3``.
+
+ The color name will be normalized to lower-case before being
+ looked up, and when no color of that name exists in the given
+ specification, ``ValueError`` is raised.
+
+ Example:
+
+ >>> name_to_rgb_percent('navy')
+ ('0%', '0%', '50%')
+
+
+Conversions from hexadecimal values
+-----------------------------------
+
+``hex_to_name(hex_value, spec='css3')``
+ Convert a hexadecimal color value to its corresponding normalized
+ color name, if any such name exists.
+
+ The optional keyword argument ``spec`` determines which
+ specification's list of color names will be used; valid values are
+ ``html4``, ``css2``, ``css21`` and ``css3``, and the default is
+ ``css3``.
+
+ The hexadecimal value will be normalized before being looked up,
+ and when no color name for the value is found in the given
+ specification, ``ValueError`` is raised.
+
+ Example:
+
+ >>> hex_to_name('#000080')
+ 'navy'
+
+``hex_to_rgb(hex_value)``
+ Convert a hexadecimal color value to a 3-tuple of integers
+ suitable for use in an ``rgb()`` triplet specifying that color.
+
+ The hexadecimal value will be normalized before being converted.
+
+ Example:
+
+ >>> hex_to_rgb('#000080')
+ (0, 0, 128)
+
+``hex_to_rgb_percent(hex_value)``
+ Convert a hexadecimal color value to a 3-tuple of percentages
+ suitable for use in an ``rgb()`` triplet representing that color.
+
+ The hexadecimal value will be normalized before converting.
+
+ Example:
+
+ >>> hex_to_rgb_percent('#ffffff')
+ ('100%', '100%', '100%')
+
+
+Conversions from integer rgb() triplets
+---------------------------------------
+
+``rgb_to_name(rgb_triplet, spec='css3')``
+ Convert a 3-tuple of integers, suitable for use in an ``rgb()``
+ color triplet, to its corresponding normalized color name, if any
+ such name exists.
+
+ The optional keyword argument ``spec`` determines which
+ specification's list of color names will be used; valid values are
+ ``html4``, ``css2``, ``css21`` and ``css3``, and the default is
+ ``css3``.
+
+ If there is no matching name, ``ValueError`` is raised.
+
+ Example:
+
+ >>> rgb_to_name((0, 0, 0))
+ 'black'
+
+``rgb_to_hex(rgb_triplet)``
+ Convert a 3-tuple of integers, suitable for use in an ``rgb()``
+ color triplet, to a normalized hexadecimal value for that color.
+
+ Example:
+
+ >>> rgb_to_hex((255, 255, 255))
+ '#ffffff'
+
+``rgb_to_rgb_percent(rgb_triplet)``
+ Convert a 3-tuple of integers, suitable for use in an ``rgb()``
+ color triplet, to a 3-tuple of percentages suitable for use in
+ representing that color.
+
+ This function makes some trade-offs in terms of the accuracy of
+ the final representation; for some common integer values,
+ special-case logic is used to ensure a precise result (e.g.,
+ integer 128 will always convert to '50%', integer 32 will always
+ convert to '12.5%'), but for all other values a standard Python
+ ``float`` is used and rounded to two decimal places, which may
+ result in a loss of precision for some values.
+
+ Examples:
+
+ >>> rgb_to_rgb_percent((255, 255, 255))
+ ('100%', '100%', '100%')
+ >>> rgb_to_rgb_percent((0, 0, 128))
+ ('0%', '0%', '50%')
+ >>> rgb_to_rgb_percent((33, 56, 192))
+ ('12.94%', '21.96%', '75.29%')
+ >>> rgb_to_rgb_percent((64, 32, 16))
+ ('25%', '12.5%', '6.25%')
+
+
+Conversions from percentage rgb() triplets
+------------------------------------------
+
+``rgb_percent_to_name(rgb_percent_triplet, spec='css3')``
+ Convert a 3-tuple of percentages, suitable for use in an ``rgb()``
+ color triplet, to its corresponding normalized color name, if any
+ such name exists.
+
+ The optional keyword argument ``spec`` determines which
+ specification's list of color names will be used; valid values are
+ ``html4``, ``css2``, ``css21`` and ``css3``, and the default is
+ ``css3``.
+
+ If there is no matching name, ``ValueError`` is raised.
+
+ Example:
+
+ >>> rgb_percent_to_name(('0%', '0%', '50%'))
+ 'navy'
+
+``rgb_percent_to_hex(rgb_percent_triplet)``
+ Convert a 3-tuple of percentages, suitable for use in an ``rgb()``
+ color triplet, to a normalized hexadecimal color value for that
+ color.
+
+ Example:
+
+ >>> rgb_percent_to_hex(('100%', '100%', '0%'))
+ '#ffff00'
+
+``rgb_percent_to_rgb(rgb_percent_triplet)``
+ Convert a 3-tuple of percentages, suitable for use in an ``rgb()``
+ color triplet, to a 3-tuple of integers suitable for use in
+ representing that color.
+
+ Some precision may be lost in this conversion. See the note
+ regarding precision for ``rgb_to_rgb_percent()`` for details;
+ generally speaking, the following is true for any 3-tuple ``t`` of
+ integers in the range 0...255 inclusive::
+
+ t == rgb_percent_to_rgb(rgb_to_rgb_percent(t))
+
+ Examples:
+
+ >>> rgb_percent_to_rgb(('100%', '100%', '100%'))
+ (255, 255, 255)
+ >>> rgb_percent_to_rgb(('0%', '0%', '50%'))
+ (0, 0, 128)
+ >>> rgb_percent_to_rgb(('25%', '12.5%', '6.25%'))
+ (64, 32, 16)
+ >>> rgb_percent_to_rgb(('12.94%', '21.96%', '75.29%'))
+ (33, 56, 192)
diff --git a/tools/kothic/mapcss/webcolors/__init__.py b/tools/kothic/mapcss/webcolors/__init__.py
new file mode 100644
index 0000000000..40a96afc6f
--- /dev/null
+++ b/tools/kothic/mapcss/webcolors/__init__.py
@@ -0,0 +1 @@
+# -*- coding: utf-8 -*-
diff --git a/tools/kothic/mapcss/webcolors/webcolors.py b/tools/kothic/mapcss/webcolors/webcolors.py
new file mode 100644
index 0000000000..6bcf7f95b3
--- /dev/null
+++ b/tools/kothic/mapcss/webcolors/webcolors.py
@@ -0,0 +1,880 @@
+# -*- coding: utf-8 -*-
+"""
+A simple library for working with the color names and color codes
+defined by the HTML and CSS specifications.
+
+An overview of HTML and CSS colors
+----------------------------------
+
+Colors on the Web are specified in `the sRGB color space`_, where each
+color is made up of a red component, a green component and a blue
+component. This is useful because it maps (fairly) cleanly to the red,
+green and blue components of pixels on a computer display, and to the
+cone cells of a human eye, which come in three sets roughly
+corresponding to the wavelengths of light associated with red, green
+and blue.
+
+`The HTML 4 standard`_ defines two ways to specify sRGB colors:
+
+* A hash mark ('#') followed by three pairs of hexdecimal digits,
+ specifying values for red, green and blue components in that order;
+ for example, ``#0099cc``. Since each pair of hexadecimal digits can
+ express 256 different values, this allows up to 256**3 or 16,777,216
+ unique colors to be specified (though, due to differences in display
+ technology, not all of these colors may be clearly distinguished on
+ any given physical display).
+
+* A set of predefined color names which correspond to specific
+ hexadecimal values; for example, ``white``. HTML 4 defines sixteen
+ such colors.
+
+`The CSS 2 standard`_ allows any valid HTML 4 color specification, and
+adds three new ways to specify sRGB colors:
+
+* A hash mark followed by three hexadecimal digits, which is expanded
+ into three hexadecimal pairs by repeating each digit; thus ``#09c``
+ is equivalent to ``#0099cc``.
+
+* The string 'rgb', followed by parentheses, between which are three
+ numeric values each between 0 and 255, inclusive, which are taken to
+ be the values of the red, green and blue components in that order;
+ for example, ``rgb(0, 153, 204)``.
+
+* The same as above, except using percentages instead of numeric
+ values; for example, ``rgb(0%, 60%, 80%)``.
+
+`The CSS 2.1 revision`_ does not add any new methods of specifying
+sRGB colors, but does add one additional named color.
+
+`The CSS 3 color module`_ (currently a W3C Candidate Recommendation)
+adds one new way to specify sRGB colors:
+
+* A hue-saturation-lightness triple (HSL), using the construct
+ ``hsl()``.
+
+It also adds support for variable opacity of colors, by allowing the
+specification of alpha-channel information, through the ``rgba()`` and
+``hsla()`` constructs, which are identical to ``rgb()`` and ``hsl()``
+with one exception: a fourth value is supplied, indicating the level
+of opacity from ``0.0`` (completely transparent) to ``1.0``
+(completely opaque). Though not technically a color, the keyword
+``transparent`` is also made available in lieu of a color value, and
+corresponds to ``rgba(0,0,0,0)``.
+
+Additionally, CSS3 defines a new set of color names; this set is taken
+directly from the named colors defined for SVG (Scalable Vector
+Graphics) markup, and is a proper superset of the named colors defined
+in CSS 2.1. This set also has significant overlap with traditional X11
+color sets as defined by the ``rgb.txt`` file on many Unix and
+Unix-like operating systems, though the correspondence is not exact;
+the set of X11 colors is not standardized, and the set of CSS3 colors
+contains some definitions which diverge significantly from customary
+X11 definitions (for example, CSS3's ``green`` is not equivalent to
+X11's ``green``; the value which X11 designates ``green`` is
+designated ``lime`` in CSS3).
+
+.. _the sRGB color space: http://www.w3.org/Graphics/Color/sRGB
+.. _The HTML 4 standard: http://www.w3.org/TR/html401/types.html#h-6.5
+.. _The CSS 2 standard: http://www.w3.org/TR/REC-CSS2/syndata.html#value-def-color
+.. _The CSS 2.1 revision: http://www.w3.org/TR/CSS21/
+.. _The CSS 3 color module: http://www.w3.org/TR/css3-color/
+
+What this module supports
+-------------------------
+
+The mappings and functions within this module support the following
+methods of specifying sRGB colors, and conversions between them:
+
+* Six-digit hexadecimal.
+
+* Three-digit hexadecimal.
+
+* Integer ``rgb()`` triplet.
+
+* Percentage ``rgb()`` triplet.
+
+* Varying selections of predefined color names (see below).
+
+This module does not support ``hsl()`` triplets, nor does it support
+opacity/alpha-channel information via ``rgba()`` or ``hsla()``.
+
+If you need to convert between RGB-specified colors and HSL-specified
+colors, or colors specified via other means, consult `the colorsys
+module`_ in the Python standard library, which can perform conversions
+amongst several common color spaces.
+
+.. _the colorsys module: http://docs.python.org/library/colorsys.html
+
+Normalization
+-------------
+
+For colors specified via hexadecimal values, this module will accept
+input in the following formats:
+
+* A hash mark (#) followed by three hexadecimal digits, where letters
+ may be upper- or lower-case.
+
+* A hash mark (#) followed by six hexadecimal digits, where letters
+ may be upper- or lower-case.
+
+For output which consists of a color specified via hexadecimal values,
+and for functions which perform intermediate conversion to hexadecimal
+before returning a result in another format, this module always
+normalizes such values to the following format:
+
+* A hash mark (#) followed by six hexadecimal digits, with letters
+ forced to lower-case.
+
+The function ``normalize_hex()`` in this module can be used to perform
+this normalization manually if desired; see its documentation for an
+explanation of the normalization process.
+
+For colors specified via predefined names, this module will accept
+input in the following formats:
+
+* An entirely lower-case name, such as ``aliceblue``.
+
+* A name using initial capitals, such as ``AliceBlue``.
+
+For output which consists of a color specified via a predefined name,
+and for functions which perform intermediate conversion to a
+predefined name before returning a result in another format, this
+module always normalizes such values to be entirely lower-case.
+
+Mappings of color names
+-----------------------
+
+For each set of defined color names -- HTML 4, CSS 2, CSS 2.1 and CSS
+3 -- this module exports two mappings: one of normalized color names
+to normalized hexadecimal values, and one of normalized hexadecimal
+values to normalized color names. These eight mappings are as follows:
+
+``html4_names_to_hex``
+ Mapping of normalized HTML 4 color names to normalized hexadecimal
+ values.
+
+``html4_hex_to_names``
+ Mapping of normalized hexadecimal values to normalized HTML 4
+ color names.
+
+``css2_names_to_hex``
+ Mapping of normalized CSS 2 color names to normalized hexadecimal
+ values. Because CSS 2 defines the same set of named colors as HTML
+ 4, this is merely an alias for ``html4_names_to_hex``.
+
+``css2_hex_to_names``
+ Mapping of normalized hexadecimal values to normalized CSS 2 color
+ nams. For the reasons described above, this is merely an alias for
+ ``html4_hex_to_names``.
+
+``css21_names_to_hex``
+ Mapping of normalized CSS 2.1 color names to normalized
+ hexadecimal values. This is identical to ``html4_names_to_hex``,
+ except for one addition: ``orange``.
+
+``css21_hex_to_names``
+ Mapping of normalized hexadecimal values to normalized CSS 2.1
+ color names. As above, this is identical to ``html4_hex_to_names``
+ except for the addition of ``orange``.
+
+``css3_names_to_hex``
+ Mapping of normalized CSS3 color names to normalized hexadecimal
+ values.
+
+``css3_hex_to_names``
+ Mapping of normalized hexadecimal values to normalized CSS3 color
+ names.
+
+"""
+
+import math
+import re
+from hashlib import md5
+
+
+def _reversedict(d):
+ """
+ Internal helper for generating reverse mappings; given a
+ dictionary, returns a new dictionary with keys and values swapped.
+
+ """
+ return dict(zip(d.values(), d.keys()))
+
+HEX_COLOR_RE = re.compile(r'^#([a-fA-F0-9]|[a-fA-F0-9]{3}|[a-fA-F0-9]{6})$')
+
+SUPPORTED_SPECIFICATIONS = ('html4', 'css2', 'css21', 'css3')
+
+
+######################################################################
+# Mappings of color names to normalized hexadecimal color values.
+######################################################################
+
+
+html4_names_to_hex = {
+ 'aqua': '#00ffff',
+ 'black': '#000000',
+ 'blue': '#0000ff',
+ 'fuchsia': '#ff00ff',
+ 'green': '#008000',
+ 'grey': '#808080',
+ 'lime': '#00ff00',
+ 'maroon': '#800000',
+ 'navy': '#000080',
+ 'olive': '#808000',
+ 'purple': '#800080',
+ 'red': '#ff0000',
+ 'silver': '#c0c0c0',
+ 'teal': '#008080',
+ 'white': '#ffffff',
+ 'yellow': '#ffff00'
+}
+
+css2_names_to_hex = html4_names_to_hex
+
+css21_names_to_hex = dict(html4_names_to_hex, orange='#ffa500')
+
+css3_names_to_hex = {
+ 'aliceblue': '#f0f8ff',
+ 'antiquewhite': '#faebd7',
+ 'aqua': '#00ffff',
+ 'aquamarine': '#7fffd4',
+ 'azure': '#f0ffff',
+ 'beige': '#f5f5dc',
+ 'bisque': '#ffe4c4',
+ 'black': '#000000',
+ 'blanchedalmond': '#ffebcd',
+ 'blue': '#0000ff',
+ 'blueviolet': '#8a2be2',
+ 'brown': '#a52a2a',
+ 'burlywood': '#deb887',
+ 'cadetblue': '#5f9ea0',
+ 'chartreuse': '#7fff00',
+ 'chocolate': '#d2691e',
+ 'coral': '#ff7f50',
+ 'cornflowerblue': '#6495ed',
+ 'cornsilk': '#fff8dc',
+ 'crimson': '#dc143c',
+ 'cyan': '#00ffff',
+ 'darkblue': '#00008b',
+ 'darkcyan': '#008b8b',
+ 'darkgoldenrod': '#b8860b',
+ 'darkgray': '#a9a9a9',
+ 'darkgrey': '#a9a9a9',
+ 'darkgreen': '#006400',
+ 'darkkhaki': '#bdb76b',
+ 'darkmagenta': '#8b008b',
+ 'darkolivegreen': '#556b2f',
+ 'darkorange': '#ff8c00',
+ 'darkorchid': '#9932cc',
+ 'darkred': '#8b0000',
+ 'darksalmon': '#e9967a',
+ 'darkseagreen': '#8fbc8f',
+ 'darkslateblue': '#483d8b',
+ 'darkslategray': '#2f4f4f',
+ 'darkslategrey': '#2f4f4f',
+ 'darkturquoise': '#00ced1',
+ 'darkviolet': '#9400d3',
+ 'deeppink': '#ff1493',
+ 'deepskyblue': '#00bfff',
+ 'dimgray': '#696969',
+ 'dimgrey': '#696969',
+ 'dodgerblue': '#1e90ff',
+ 'firebrick': '#b22222',
+ 'floralwhite': '#fffaf0',
+ 'forestgreen': '#228b22',
+ 'fuchsia': '#ff00ff',
+ 'gainsboro': '#dcdcdc',
+ 'ghostwhite': '#f8f8ff',
+ 'gold': '#ffd700',
+ 'goldenrod': '#daa520',
+ 'gray': '#808080',
+ 'grey': '#808080',
+ 'green': '#008000',
+ 'greenyellow': '#adff2f',
+ 'honeydew': '#f0fff0',
+ 'hotpink': '#ff69b4',
+ 'indianred': '#cd5c5c',
+ 'indigo': '#4b0082',
+ 'ivory': '#fffff0',
+ 'khaki': '#f0e68c',
+ 'lavender': '#e6e6fa',
+ 'lavenderblush': '#fff0f5',
+ 'lawngreen': '#7cfc00',
+ 'lemonchiffon': '#fffacd',
+ 'lightblue': '#add8e6',
+ 'lightcoral': '#f08080',
+ 'lightcyan': '#e0ffff',
+ 'lightgoldenrodyellow': '#fafad2',
+ 'lightgray': '#d3d3d3',
+ 'lightgrey': '#d3d3d3',
+ 'lightgreen': '#90ee90',
+ 'lightpink': '#ffb6c1',
+ 'lightsalmon': '#ffa07a',
+ 'lightseagreen': '#20b2aa',
+ 'lightskyblue': '#87cefa',
+ 'lightslategray': '#778899',
+ 'lightslategrey': '#778899',
+ 'lightsteelblue': '#b0c4de',
+ 'lightyellow': '#ffffe0',
+ 'lime': '#00ff00',
+ 'limegreen': '#32cd32',
+ 'linen': '#faf0e6',
+ 'magenta': '#ff00ff',
+ 'maroon': '#800000',
+ 'mediumaquamarine': '#66cdaa',
+ 'mediumblue': '#0000cd',
+ 'mediumorchid': '#ba55d3',
+ 'mediumpurple': '#9370d8',
+ 'mediumseagreen': '#3cb371',
+ 'mediumslateblue': '#7b68ee',
+ 'mediumspringgreen': '#00fa9a',
+ 'mediumturquoise': '#48d1cc',
+ 'mediumvioletred': '#c71585',
+ 'midnightblue': '#191970',
+ 'mintcream': '#f5fffa',
+ 'mistyrose': '#ffe4e1',
+ 'moccasin': '#ffe4b5',
+ 'navajowhite': '#ffdead',
+ 'navy': '#000080',
+ 'oldlace': '#fdf5e6',
+ 'olive': '#808000',
+ 'olivedrab': '#6b8e23',
+ 'orange': '#ffa500',
+ 'orangered': '#ff4500',
+ 'orchid': '#da70d6',
+ 'palegoldenrod': '#eee8aa',
+ 'palegreen': '#98fb98',
+ 'paleturquoise': '#afeeee',
+ 'palevioletred': '#d87093',
+ 'papayawhip': '#ffefd5',
+ 'peachpuff': '#ffdab9',
+ 'peru': '#cd853f',
+ 'pink': '#ffc0cb',
+ 'plum': '#dda0dd',
+ 'powderblue': '#b0e0e6',
+ 'purple': '#800080',
+ 'red': '#ff0000',
+ 'rosybrown': '#bc8f8f',
+ 'royalblue': '#4169e1',
+ 'saddlebrown': '#8b4513',
+ 'salmon': '#fa8072',
+ 'sandybrown': '#f4a460',
+ 'seagreen': '#2e8b57',
+ 'seashell': '#fff5ee',
+ 'sienna': '#a0522d',
+ 'silver': '#c0c0c0',
+ 'skyblue': '#87ceeb',
+ 'slateblue': '#6a5acd',
+ 'slategray': '#708090',
+ 'slategrey': '#708090',
+ 'snow': '#fffafa',
+ 'springgreen': '#00ff7f',
+ 'steelblue': '#4682b4',
+ 'tan': '#d2b48c',
+ 'teal': '#008080',
+ 'thistle': '#d8bfd8',
+ 'tomato': '#ff6347',
+ 'turquoise': '#40e0d0',
+ 'violet': '#ee82ee',
+ 'wheat': '#f5deb3',
+ 'white': '#ffffff',
+ 'whitesmoke': '#f5f5f5',
+ 'yellow': '#ffff00',
+ 'yellowgreen': '#9acd32',
+}
+
+
+######################################################################
+# Mappings of normalized hexadecimal color values to color names.
+######################################################################
+
+
+html4_hex_to_names = _reversedict(html4_names_to_hex)
+
+css2_hex_to_names = html4_hex_to_names
+
+css21_hex_to_names = _reversedict(css21_names_to_hex)
+
+css3_hex_to_names = _reversedict(css3_names_to_hex)
+
+
+######################################################################
+# Normalization routines.
+######################################################################
+
+
+def normalize_hex(hex_value):
+ """
+ Normalize a hexadecimal color value to the following form and
+ return the result::
+
+ #[a-f0-9]{6}
+
+ In other words, the following transformations are applied as
+ needed:
+
+ * If the value contains only three hexadecimal digits, it is
+ expanded to six.
+
+ * The value is normalized to lower-case.
+
+ If the supplied value cannot be interpreted as a hexadecimal color
+ value, ``ValueError`` is raised.
+
+ Examples:
+
+ >>> normalize_hex('#09c')
+ '#0099cc'
+ >>> normalize_hex('#0099cc')
+ '#0099cc'
+ >>> normalize_hex('#09C')
+ '#0099cc'
+ >>> normalize_hex('#0099CC')
+ '#0099cc'
+ >>> normalize_hex('0099cc')
+ Traceback (most recent call last):
+ ...
+ ValueError: '0099cc' is not a valid hexadecimal color value.
+ >>> normalize_hex('#0099QX')
+ Traceback (most recent call last):
+ ...
+ ValueError: '#0099QX' is not a valid hexadecimal color value.
+ >>> normalize_hex('foobarbaz')
+ Traceback (most recent call last):
+ ...
+ ValueError: 'foobarbaz' is not a valid hexadecimal color value.
+ >>> normalize_hex('#0')
+ Traceback (most recent call last):
+ ...
+ ValueError: '#0' is not a valid hexadecimal color value.
+
+ """
+ try:
+ hex_digits = HEX_COLOR_RE.match(hex_value).groups()[0]
+ except AttributeError:
+ raise ValueError("'%s' is not a valid hexadecimal color value." % hex_value)
+ if len(hex_digits) == 3:
+ hex_digits = ''.join(map(lambda s: 2 * s, hex_digits))
+ elif len(hex_digits) == 1:
+ hex_digits = hex_digits * 6
+ return '#%s' % hex_digits.lower()
+
+
+######################################################################
+# Conversions from color names to various formats.
+######################################################################
+
+
+def name_to_hex(name, spec='css3'):
+ """
+ Convert a color name to a normalized hexadecimal color value.
+
+ The optional keyword argument ``spec`` determines which
+ specification's list of color names will be used; valid values are
+ ``html4``, ``css2``, ``css21`` and ``css3``, and the default is
+ ``css3``.
+
+ The color name will be normalized to lower-case before being
+ looked up, and when no color of that name exists in the given
+ specification, ``ValueError`` is raised.
+
+ Examples:
+
+ >>> name_to_hex('deepskyblue')
+ '#00bfff'
+ >>> name_to_hex('DeepSkyBlue')
+ '#00bfff'
+ >>> name_to_hex('white', spec='html4')
+ '#ffffff'
+ >>> name_to_hex('white', spec='css2')
+ '#ffffff'
+ >>> name_to_hex('white', spec='css21')
+ '#ffffff'
+ >>> name_to_hex('white', spec='css3')
+ '#ffffff'
+ >>> name_to_hex('white', spec='css4')
+ Traceback (most recent call last):
+ ...
+ TypeError: 'css4' is not a supported specification for color name lookups; supported specifications are: html4, css2, css21, css3.
+ >>> name_to_hex('deepskyblue', spec='css2')
+ Traceback (most recent call last):
+ ...
+ ValueError: 'deepskyblue' is not defined as a named color in css2.
+
+ """
+ if spec not in SUPPORTED_SPECIFICATIONS:
+ raise TypeError("'%s' is not a supported specification for color name lookups; supported specifications are: %s." % (spec,
+ ', '.join(SUPPORTED_SPECIFICATIONS)))
+ normalized = name.lower()
+ try:
+ hex_value = globals()['%s_names_to_hex' % spec][normalized]
+ except KeyError:
+ raise ValueError("'%s' is not defined as a named color in %s." % (name, spec))
+ return hex_value
+
+
+def name_to_rgb(name, spec='css3'):
+ """
+ Convert a color name to a 3-tuple of integers suitable for use in
+ an ``rgb()`` triplet specifying that color.
+
+ The optional keyword argument ``spec`` determines which
+ specification's list of color names will be used; valid values are
+ ``html4``, ``css2``, ``css21`` and ``css3``, and the default is
+ ``css3``.
+
+ The color name will be normalized to lower-case before being
+ looked up, and when no color of that name exists in the given
+ specification, ``ValueError`` is raised.
+
+ Examples:
+
+ >>> name_to_rgb('navy')
+ (0, 0, 128)
+ >>> name_to_rgb('cadetblue')
+ (95, 158, 160)
+ >>> name_to_rgb('cadetblue', spec='html4')
+ Traceback (most recent call last):
+ ...
+ ValueError: 'cadetblue' is not defined as a named color in html4.
+
+ """
+ return hex_to_rgb(name_to_hex(name, spec=spec))
+
+
+def name_to_rgb_percent(name, spec='css3'):
+ """
+ Convert a color name to a 3-tuple of percentages suitable for use
+ in an ``rgb()`` triplet specifying that color.
+
+ The optional keyword argument ``spec`` determines which
+ specification's list of color names will be used; valid values are
+ ``html4``, ``css2``, ``css21`` and ``css3``, and the default is
+ ``css3``.
+
+ The color name will be normalized to lower-case before being
+ looked up, and when no color of that name exists in the given
+ specification, ``ValueError`` is raised.
+
+ Examples:
+
+ >>> name_to_rgb_percent('white')
+ ('100%', '100%', '100%')
+ >>> name_to_rgb_percent('navy')
+ ('0%', '0%', '50%')
+ >>> name_to_rgb_percent('goldenrod')
+ ('85.49%', '64.71%', '12.5%')
+
+ """
+ return rgb_to_rgb_percent(name_to_rgb(name, spec=spec))
+
+
+######################################################################
+# Conversions from hexadecimal color values to various formats.
+######################################################################
+
+
+def hex_to_name(hex_value, spec='css3'):
+ """
+ Convert a hexadecimal color value to its corresponding normalized
+ color name, if any such name exists.
+
+ The optional keyword argument ``spec`` determines which
+ specification's list of color names will be used; valid values are
+ ``html4``, ``css2``, ``css21`` and ``css3``, and the default is
+ ``css3``.
+
+ The hexadecimal value will be normalized before being looked up,
+ and when no color name for the value is found in the given
+ specification, ``ValueError`` is raised.
+
+ Examples:
+
+ >>> hex_to_name('#000080')
+ 'navy'
+ >>> hex_to_name('#000080', spec='html4')
+ 'navy'
+ >>> hex_to_name('#000080', spec='css2')
+ 'navy'
+ >>> hex_to_name('#000080', spec='css21')
+ 'navy'
+ >>> hex_to_name('#8b4513')
+ 'saddlebrown'
+ >>> hex_to_name('#8b4513', spec='html4')
+ Traceback (most recent call last):
+ ...
+ ValueError: '#8b4513' has no defined color name in html4.
+ >>> hex_to_name('#8b4513', spec='css4')
+ Traceback (most recent call last):
+ ...
+ TypeError: 'css4' is not a supported specification for color name lookups; supported specifications are: html4, css2, css21, css3.
+
+ """
+ if spec not in SUPPORTED_SPECIFICATIONS:
+ raise TypeError("'%s' is not a supported specification for color name lookups; supported specifications are: %s." % (spec,
+ ', '.join(SUPPORTED_SPECIFICATIONS)))
+ normalized = normalize_hex(hex_value)
+ try:
+ name = globals()['%s_hex_to_names' % spec][normalized]
+ except KeyError:
+ raise ValueError("'%s' has no defined color name in %s." % (hex_value, spec))
+ return name
+
+
+def any_hex_to_name(hex_value):
+ try:
+ return hex_to_name(hex_value)
+ except ValueError:
+ return hex_value
+
+
+def hex_to_rgb(hex_value):
+ """
+ Convert a hexadecimal color value to a 3-tuple of integers
+ suitable for use in an ``rgb()`` triplet specifying that color.
+
+ The hexadecimal value will be normalized before being converted.
+
+ Examples:
+
+ >>> hex_to_rgb('#000080')
+ (0, 0, 128)
+ >>> hex_to_rgb('#ffff00')
+ (255, 255, 0)
+ >>> hex_to_rgb('#f00')
+ (255, 0, 0)
+ >>> hex_to_rgb('#deb887')
+ (222, 184, 135)
+
+ """
+ hex_digits = normalize_hex(hex_value)
+ return tuple(map(lambda s: int(s, 16),
+ (hex_digits[1:3], hex_digits[3:5], hex_digits[5:7])))
+
+
+def hex_to_rgb_percent(hex_value):
+ """
+ Convert a hexadecimal color value to a 3-tuple of percentages
+ suitable for use in an ``rgb()`` triplet representing that color.
+
+ The hexadecimal value will be normalized before converting.
+
+ Examples:
+
+ >>> hex_to_rgb_percent('#ffffff')
+ ('100%', '100%', '100%')
+ >>> hex_to_rgb_percent('#000080')
+ ('0%', '0%', '50%')
+
+ """
+ return rgb_to_rgb_percent(hex_to_rgb(hex_value))
+
+
+######################################################################
+# Conversions from integer rgb() triplets to various formats.
+######################################################################
+
+
+def rgb_to_name(rgb_triplet, spec='css3'):
+ """
+ Convert a 3-tuple of integers, suitable for use in an ``rgb()``
+ color triplet, to its corresponding normalized color name, if any
+ such name exists.
+
+ The optional keyword argument ``spec`` determines which
+ specification's list of color names will be used; valid values are
+ ``html4``, ``css2``, ``css21`` and ``css3``, and the default is
+ ``css3``.
+
+ If there is no matching name, ``ValueError`` is raised.
+
+ Examples:
+
+ >>> rgb_to_name((0, 0, 0))
+ 'black'
+ >>> rgb_to_name((0, 0, 128))
+ 'navy'
+ >>> rgb_to_name((95, 158, 160))
+ 'cadetblue'
+
+ """
+ return hex_to_name(rgb_to_hex(rgb_triplet), spec=spec)
+
+
+def rgb_to_hex(rgb_triplet):
+ """
+ Convert a 3-tuple of integers, suitable for use in an ``rgb()``
+ color triplet, to a normalized hexadecimal value for that color.
+
+ Examples:
+
+ >>> rgb_to_hex((255, 255, 255))
+ '#ffffff'
+ >>> rgb_to_hex((0, 0, 128))
+ '#000080'
+ >>> rgb_to_hex((33, 56, 192))
+ '#2138c0'
+
+ """
+ return '#%02x%02x%02x' % rgb_triplet
+
+
+def rgb_to_rgb_percent(rgb_triplet):
+ """
+ Convert a 3-tuple of integers, suitable for use in an ``rgb()``
+ color triplet, to a 3-tuple of percentages suitable for use in
+ representing that color.
+
+ This function makes some trade-offs in terms of the accuracy of
+ the final representation; for some common integer values,
+ special-case logic is used to ensure a precise result (e.g.,
+ integer 128 will always convert to '50%', integer 32 will always
+ convert to '12.5%'), but for all other values a standard Python
+ ``float`` is used and rounded to two decimal places, which may
+ result in a loss of precision for some values.
+
+ Examples:
+
+ >>> rgb_to_rgb_percent((255, 255, 255))
+ ('100%', '100%', '100%')
+ >>> rgb_to_rgb_percent((0, 0, 128))
+ ('0%', '0%', '50%')
+ >>> rgb_to_rgb_percent((33, 56, 192))
+ ('12.94%', '21.96%', '75.29%')
+ >>> rgb_to_rgb_percent((64, 32, 16))
+ ('25%', '12.5%', '6.25%')
+
+ """
+ # In order to maintain precision for common values,
+ # 256 / 2**n is special-cased for values of n
+ # from 0 through 4, as well as 0 itself.
+ specials = {255: '100%', 128: '50%', 64: '25%',
+ 32: '12.5%', 16: '6.25%', 0: '0%'}
+ return tuple(map(lambda d: specials.get(d, '%.02f%%' % ((d / 255.0) * 100)),
+ rgb_triplet))
+
+
+######################################################################
+# Conversions from percentage rgb() triplets to various formats.
+######################################################################
+
+
+def rgb_percent_to_name(rgb_percent_triplet, spec='css3'):
+ """
+ Convert a 3-tuple of percentages, suitable for use in an ``rgb()``
+ color triplet, to its corresponding normalized color name, if any
+ such name exists.
+
+ The optional keyword argument ``spec`` determines which
+ specification's list of color names will be used; valid values are
+ ``html4``, ``css2``, ``css21`` and ``css3``, and the default is
+ ``css3``.
+
+ If there is no matching name, ``ValueError`` is raised.
+
+ Examples:
+
+ >>> rgb_percent_to_name(('0%', '0%', '0%'))
+ 'black'
+ >>> rgb_percent_to_name(('0%', '0%', '50%'))
+ 'navy'
+ >>> rgb_percent_to_name(('85.49%', '64.71%', '12.5%'))
+ 'goldenrod'
+
+ """
+ return rgb_to_name(rgb_percent_to_rgb(rgb_percent_triplet), spec=spec)
+
+
+def rgb_percent_to_hex(rgb_percent_triplet):
+ """
+ Convert a 3-tuple of percentages, suitable for use in an ``rgb()``
+ color triplet, to a normalized hexadecimal color value for that
+ color.
+
+ Examples:
+
+ >>> rgb_percent_to_hex(('100%', '100%', '0%'))
+ '#ffff00'
+ >>> rgb_percent_to_hex(('0%', '0%', '50%'))
+ '#000080'
+ >>> rgb_percent_to_hex(('85.49%', '64.71%', '12.5%'))
+ '#daa520'
+
+ """
+ return rgb_to_hex(rgb_percent_to_rgb(rgb_percent_triplet))
+
+
+def _percent_to_integer(percent):
+ """
+ Internal helper for converting a percentage value to an integer
+ between 0 and 255 inclusive.
+
+ """
+ num = float(percent.split('%')[0]) / 100.0 * 255
+ e = num - math.floor(num)
+ return e < 0.5 and int(math.floor(num)) or int(math.ceil(num))
+
+
+def rgb_percent_to_rgb(rgb_percent_triplet):
+ """
+ Convert a 3-tuple of percentages, suitable for use in an ``rgb()``
+ color triplet, to a 3-tuple of integers suitable for use in
+ representing that color.
+
+ Some precision may be lost in this conversion. See the note
+ regarding precision for ``rgb_to_rgb_percent()`` for details;
+ generally speaking, the following is true for any 3-tuple ``t`` of
+ integers in the range 0...255 inclusive::
+
+ t == rgb_percent_to_rgb(rgb_to_rgb_percent(t))
+
+ Examples:
+
+ >>> rgb_percent_to_rgb(('100%', '100%', '100%'))
+ (255, 255, 255)
+ >>> rgb_percent_to_rgb(('0%', '0%', '50%'))
+ (0, 0, 128)
+ >>> rgb_percent_to_rgb(('25%', '12.5%', '6.25%'))
+ (64, 32, 16)
+ >>> rgb_percent_to_rgb(('12.94%', '21.96%', '75.29%'))
+ (33, 56, 192)
+
+ """
+ return tuple(map(_percent_to_integer, rgb_percent_triplet))
+
+
+def whatever_to_rgb(string):
+ """
+ Converts CSS3 color or a hex into rgb triplet; hash of string if fails.
+ """
+ string = string.strip().lower()
+ try:
+ return name_to_rgb(string)
+ except ValueError:
+ try:
+ return hex_to_rgb(string)
+ except ValueError:
+ try:
+ if string[:3] == "rgb":
+ return tuple([float(i) for i in string[4:-1].split(",")][0:3])
+ except:
+ return hex_to_rgb("#" + md5(string).hexdigest()[:6])
+
+
+def whatever_to_hex(string):
+ if type(string) == tuple:
+ return cairo_to_hex(string).upper()
+ return rgb_to_hex(whatever_to_rgb(string)).upper()
+
+
+def whatever_to_cairo(string):
+ a = whatever_to_rgb(string)
+ return a[0] / 255., a[1] / 255., a[2] / 255.
+
+
+def cairo_to_hex(cairo):
+ return rgb_to_hex((cairo[0] * 255., cairo[1] * 255., cairo[2] * 255.))
+
+if __name__ == '__main__':
+ import doctest
+ doctest.testmod()
diff --git a/tools/kothic/timer.py b/tools/kothic/timer.py
new file mode 100644
index 0000000000..5da95b7c5f
--- /dev/null
+++ b/tools/kothic/timer.py
@@ -0,0 +1,42 @@
+from timeit import default_timer
+
+class Timer(object):
+ def __init__(self):
+ self.timer = default_timer
+ self.start = self.timer()
+
+ def Reset(self):
+ self.start = self.timer()
+
+ def ElapsedMsec(self):
+ elapsed_secs = self.timer() - self.start
+ return elapsed_secs * 1000
+
+ def ElapsedSec(self):
+ elapsed_secs = self.timer() - self.start
+ return elapsed_secs
+
+class AccumulativeTimer(object):
+ def __init__(self):
+ self.timer = default_timer
+ self.elapsed_secs = 0
+ self.start = 0
+ self.count = 0
+
+ def Start(self):
+ self.start = self.timer()
+
+ def Stop(self):
+ self.elapsed_secs += self.timer() - self.start
+ self.start = 0
+ self.count += 1
+
+ def ElapsedMsec(self):
+ elapsed_msec = self.elapsed_secs * 1000
+ return elapsed_msec
+
+ def ElapsedSec(self):
+ return self.elapsed_secs
+
+ def Count(self):
+ return self.count
diff --git a/tools/unix/generate_drules.sh b/tools/unix/generate_drules.sh
new file mode 100755
index 0000000000..78c7c21e15
--- /dev/null
+++ b/tools/unix/generate_drules.sh
@@ -0,0 +1,5 @@
+echo "Build drawing rules light"
+python ../kothic/libkomwm.py -s ../../data/styles/normal_light.mapcss -o ../../data/drules_proto
+
+echo "Build drawing rules dark"
+python ../kothic/libkomwm.py -s ../../data/styles/normal_dark.mapcss -o ../../data/drules_proto_dark