From 9c485dbd4e028fca2b44b945aad0e9a335da9f3d Mon Sep 17 00:00:00 2001 From: Anand Chitipothu Date: Wed, 17 Mar 2021 11:40:04 +0000 Subject: [PATCH 1/8] Added support for line and rect in the preview of sketches --- community/lms/doctype/lms_sketch/livecode.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/community/lms/doctype/lms_sketch/livecode.py b/community/lms/doctype/lms_sketch/livecode.py index 1eb804ad..4363efc7 100644 --- a/community/lms/doctype/lms_sketch/livecode.py +++ b/community/lms/doctype/lms_sketch/livecode.py @@ -41,4 +41,8 @@ def draw_image(commands): for c in commands: if c['function'] == 'circle': img.append(draw.Circle(cx=c['x'], cy=c['y'], r=c['d']/2)) + elif c['function'] == 'line': + img.append(draw.Line(x1=c['x1'], y1=c['y1'], x2=c['x2'], y2=c['y2'])) + elif c['function'] == 'rect': + img.append(draw.Rectangle(x=c['x'], y=c['y'], width=c['w'], height=c['h'])) return img From f8ba10dfbaa964cb6a5cf02e2187797e05b65340 Mon Sep 17 00:00:00 2001 From: Anand Chitipothu Date: Wed, 17 Mar 2021 11:40:29 +0000 Subject: [PATCH 2/8] Include sketch title the sketch listing page. --- community/public/css/lms.css | 5 +++++ community/www/sketches/index.html | 9 +++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/community/public/css/lms.css b/community/public/css/lms.css index 590f8160..004767ee 100644 --- a/community/public/css/lms.css +++ b/community/public/css/lms.css @@ -62,3 +62,8 @@ .sketch-header { margin-bottom: 1em; } + +.sketch-card .sketch-title a { + font-weight: bold; + color: inherit; +} diff --git a/community/www/sketches/index.html b/community/www/sketches/index.html index 5da1037e..e99ff675 100644 --- a/community/www/sketches/index.html +++ b/community/www/sketches/index.html @@ -19,14 +19,19 @@
{% for sketch in sketches %}
-
+
From 03a22bd53727862b3480895f0d174fe85b6cac13 Mon Sep 17 00:00:00 2001 From: Anand Chitipothu Date: Tue, 23 Mar 2021 04:20:54 +0000 Subject: [PATCH 3/8] Switched to using `frappe.utils.md_to_html` for rendering markdown. The `markdown` filter doesn't convert to html if there is any html tag in the input. --- community/www/courses/topic.html | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/community/www/courses/topic.html b/community/www/courses/topic.html index 205bbeee..e7008baa 100644 --- a/community/www/courses/topic.html +++ b/community/www/courses/topic.html @@ -51,7 +51,9 @@ {% endmacro %} {% macro render_section_text(s) %} - {{ s.contents | markdown }} +

BEGIN SECTION

+ {{ frappe.utils.md_to_html(s.contents) }} +

END SECTION

{% endmacro %} {%- block script %} From 071df674c0a0134535a48568df8cd6fb28fefff9 Mon Sep 17 00:00:00 2001 From: Anand Chitipothu Date: Tue, 23 Mar 2021 04:23:52 +0000 Subject: [PATCH 4/8] Removed the debug text in rendering topic pages. --- community/www/courses/topic.html | 2 -- 1 file changed, 2 deletions(-) diff --git a/community/www/courses/topic.html b/community/www/courses/topic.html index e7008baa..e2817d1e 100644 --- a/community/www/courses/topic.html +++ b/community/www/courses/topic.html @@ -51,9 +51,7 @@ {% endmacro %} {% macro render_section_text(s) %} -

BEGIN SECTION

{{ frappe.utils.md_to_html(s.contents) }} -

END SECTION

{% endmacro %} {%- block script %} From dfafab3ccdd804cd1eda6a4ec753bc4ea5127643 Mon Sep 17 00:00:00 2001 From: Anand Chitipothu Date: Tue, 23 Mar 2021 04:26:24 +0000 Subject: [PATCH 5/8] Fixed the orientation issue with rendering sketch as svg. The sketches were flipped vertically when displayed as svg. It was due to the defaults in the drawSvg library that was using bottom-left corner as the origin by default. Fixed it by specifying the top-right as the origin. --- community/lms/doctype/lms_sketch/livecode.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/community/lms/doctype/lms_sketch/livecode.py b/community/lms/doctype/lms_sketch/livecode.py index 4363efc7..0069252d 100644 --- a/community/lms/doctype/lms_sketch/livecode.py +++ b/community/lms/doctype/lms_sketch/livecode.py @@ -37,7 +37,7 @@ def _read_messages(ws): return messages def draw_image(commands): - img = draw.Drawing(300, 300, fill='none', stroke='black') + img = draw.Drawing(300, 300, origin=(0, -300), fill='none', stroke='black') for c in commands: if c['function'] == 'circle': img.append(draw.Circle(cx=c['x'], cy=c['y'], r=c['d']/2)) From 280cfb3994a4b47f568c80ac357652b0c5b4e7b7 Mon Sep 17 00:00:00 2001 From: Anand Chitipothu Date: Tue, 23 Mar 2021 06:06:25 +0000 Subject: [PATCH 6/8] Fixed an error in rendering a line. Used the wrong arguments to line functions, fixed now. --- community/lms/doctype/lms_sketch/livecode.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/community/lms/doctype/lms_sketch/livecode.py b/community/lms/doctype/lms_sketch/livecode.py index 0069252d..91879000 100644 --- a/community/lms/doctype/lms_sketch/livecode.py +++ b/community/lms/doctype/lms_sketch/livecode.py @@ -42,7 +42,7 @@ def draw_image(commands): if c['function'] == 'circle': img.append(draw.Circle(cx=c['x'], cy=c['y'], r=c['d']/2)) elif c['function'] == 'line': - img.append(draw.Line(x1=c['x1'], y1=c['y1'], x2=c['x2'], y2=c['y2'])) + img.append(draw.Line(sx=c['x1'], sy=c['y1'], ex=c['x2'], ey=c['y2'])) elif c['function'] == 'rect': img.append(draw.Rectangle(x=c['x'], y=c['y'], width=c['w'], height=c['h'])) return img From ed0f70e3ed056fbe49d74dec2fc6048d23228949 Mon Sep 17 00:00:00 2001 From: Anand Chitipothu Date: Tue, 23 Mar 2021 07:11:22 +0000 Subject: [PATCH 7/8] Replaced drawSvg library with custom svg library. Even after so many attempts to fix drawSvg, couldn't get it to work with top-left corner as the origin. Replaced it with a custom library. --- community/lms/doctype/lms_sketch/livecode.py | 12 +- community/lms/doctype/lms_sketch/svg.py | 143 +++++++++++++++++++ requirements.txt | 1 - 3 files changed, 149 insertions(+), 7 deletions(-) create mode 100644 community/lms/doctype/lms_sketch/svg.py diff --git a/community/lms/doctype/lms_sketch/livecode.py b/community/lms/doctype/lms_sketch/livecode.py index 91879000..6815c269 100644 --- a/community/lms/doctype/lms_sketch/livecode.py +++ b/community/lms/doctype/lms_sketch/livecode.py @@ -2,7 +2,7 @@ """ import websocket import json -import drawSvg as draw +from .svg import SVG def livecode_to_svg(livecode_ws_url, code, *, timeout=1): """Renders the code as svg. @@ -22,7 +22,7 @@ def livecode_to_svg(livecode_ws_url, code, *, timeout=1): messages = _read_messages(ws) commands = [m['cmd'] for m in messages if m['msgtype'] == 'draw'] img = draw_image(commands) - return img.asSvg() + return img.tostring() def _read_messages(ws): messages = [] @@ -37,12 +37,12 @@ def _read_messages(ws): return messages def draw_image(commands): - img = draw.Drawing(300, 300, origin=(0, -300), fill='none', stroke='black') + img = SVG(width=300, height=300, fill='none', stroke='black') for c in commands: if c['function'] == 'circle': - img.append(draw.Circle(cx=c['x'], cy=c['y'], r=c['d']/2)) + img.circle(cx=c['x'], cy=c['y'], r=c['d']/2) elif c['function'] == 'line': - img.append(draw.Line(sx=c['x1'], sy=c['y1'], ex=c['x2'], ey=c['y2'])) + img.line(sx=c['x1'], sy=c['y1'], ex=c['x2'], ey=c['y2']) elif c['function'] == 'rect': - img.append(draw.Rectangle(x=c['x'], y=c['y'], width=c['w'], height=c['h'])) + img.rect(x=c['x'], y=c['y'], width=c['w'], height=c['h']) return img diff --git a/community/lms/doctype/lms_sketch/svg.py b/community/lms/doctype/lms_sketch/svg.py new file mode 100644 index 00000000..596be7ef --- /dev/null +++ b/community/lms/doctype/lms_sketch/svg.py @@ -0,0 +1,143 @@ +"""SVG rendering library. + +USAGE: + from svg import SVG + + svg = SVG(width=200, height=200) + svg.circle(cx=100, cy=200, r=50) + print(svg.tostring()) +""" +from xml.etree import ElementTree + +TAGNAMES = set([ + "circle", "ellipse", + "line", "path", "rect", "polygon", "polyline", + "text", "textPath", "title", + "marker", "defs", + "g" +]) + +class Node: + """SVG Node""" + def __init__(self, tag, **attrs): + self.tag = tag + self.attrs = dict((k.replace('_', '-'), str(v)) for k, v in attrs.items()) + self.children = [] + + def node(self, tag, **attrs): + n = Node(tag, **attrs) + self.children.append(n) + return n + + def apply(self, func): + """Applies a function to this node and + all the children recursively. + """ + func(self) + for n in self.children: + n.apply(func) + + def clone(self): + node = Node(self.tag, **self.attrs) + node.children = [n.clone() for n in self.children] + return node + + def add_node(self, node): + if not isinstance(node, Node): + node = Text(node) + self.children.append(node) + + def __getattr__(self, tag): + if tag not in TAGNAMES: + raise AttributeError(tag) + return lambda **attrs: self.node(tag, **attrs) + + def translate(self, x, y): + return self.g(transform="translate(%s, %s)" % (x, y)) + + def scale(self, *args): + return self.g(transform="scale(%s)" % ", ".join(str(a) for a in args)) + + def __repr__(self): + return "<%s .../>" % self.tag + + def __enter__(self): + return self + + def __exit__(self, type, value, traceback): + pass + + def build_tree(self, builder): + builder.start(self.tag, self.attrs) + for node in self.children: + node.build_tree(builder) + return builder.end(self.tag) + + def _indent(self, elem, level=0): + """Indent etree node for prettyprinting.""" + + i = "\n" + level*" " + if len(elem): + if not elem.text or not elem.text.strip(): + elem.text = i + " " + if not elem.tail or not elem.tail.strip(): + elem.tail = i + for elem in elem: + self._indent(elem, level+1) + if not elem.tail or not elem.tail.strip(): + elem.tail = i + else: + if level and (not elem.tail or not elem.tail.strip()): + elem.tail = i + + def save(self, filename, encoding='utf-8'): + f = open(filename, 'w') + f.write(self.tostring()) + f.close() + + def tostring(self, encoding='utf-8'): + builder = ElementTree.TreeBuilder() + self.build_tree(builder) + e = builder.close() + self._indent(e) + return ElementTree.tostring(e, encoding).decode(encoding) + +class Text(Node): + """Text Node + + >>> p = Node("p") + >>> p.add_node("hello, world!") + >>> p.tostring() + '

hello, world!

' + """ + def __init__(self, text): + Node.__init__(self, "__text__") + self._text = text + + def build_tree(self, builder): + builder.data(str(self._text)) + +class SVG(Node): + """ + >>> svg = SVG(width=200, height=200) + >>> svg.rect(x=0, y=0, width=200, height=200, fill="blue") + + >>> with svg.translate(-50, -50) as g: + ... g.rect(x=0, y=0, width=50, height=100, fill="red") + ... g.rect(x=50, y=0, width=50, height=100, fill="green") + + + >>> print(svg.tostring()) + + + + + + + + + """ + def __init__(self, **attrs): + attrs['xmlns'] = "http://www.w3.org/2000/svg" + Node.__init__(self, 'svg', **attrs) + diff --git a/requirements.txt b/requirements.txt index 6da7c51b..900c38db 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,2 @@ frappe websocket_client -drawSvg From 7ecfa6b0c3c98e817783e5c56526ec18fd8f5cfd Mon Sep 17 00:00:00 2001 From: Anand Chitipothu Date: Tue, 23 Mar 2021 07:22:13 +0000 Subject: [PATCH 8/8] tweaks to make the svg rendering work as expected. Set the viewBox and fixed the args to line function. --- community/lms/doctype/lms_sketch/livecode.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/community/lms/doctype/lms_sketch/livecode.py b/community/lms/doctype/lms_sketch/livecode.py index 6815c269..512f48ec 100644 --- a/community/lms/doctype/lms_sketch/livecode.py +++ b/community/lms/doctype/lms_sketch/livecode.py @@ -37,12 +37,12 @@ def _read_messages(ws): return messages def draw_image(commands): - img = SVG(width=300, height=300, fill='none', stroke='black') + img = SVG(width=300, height=300, viewBox="0 0 300 300", fill='none', stroke='black') for c in commands: if c['function'] == 'circle': img.circle(cx=c['x'], cy=c['y'], r=c['d']/2) elif c['function'] == 'line': - img.line(sx=c['x1'], sy=c['y1'], ex=c['x2'], ey=c['y2']) + img.line(x1=c['x1'], y1=c['y1'], x2=c['x2'], y2=c['y2']) elif c['function'] == 'rect': img.rect(x=c['x'], y=c['y'], width=c['w'], height=c['h']) return img