diff --git a/boxes/__init__.py b/boxes/__init__.py index 09c78ad..3fdbe50 100755 --- a/boxes/__init__.py +++ b/boxes/__init__.py @@ -1279,7 +1279,7 @@ class Boxes: self.corner(-a) @restore - def text(self, text, x=0, y=0, angle=0, align="", fontsize=10, color=[0.0, 0.0, 0.0]): + def text(self, text, x=0, y=0, angle=0, align="", fontsize=10, color=[0.0, 0.0, 0.0], font="Arial"): """ Draw text @@ -1315,7 +1315,7 @@ class Boxes: raise ValueError("Unknown alignment: %s" % align) for line in reversed(text): - self.ctx.show_text(line, fs=fontsize, align=halign, rgb=color) + self.ctx.show_text(line, fs=fontsize, align=halign, rgb=color, font=font) self.moveTo(0, 1.4 * fontsize) tx_sizes = { @@ -1879,7 +1879,7 @@ class Boxes: if 2*i in ignore_widths: l += edges[i+1].startwidth() e2 = self.edges["e"] - if 2*i+1in ignore_widths: + if 2*i+1 in ignore_widths: e1 = self.edges["e"] edges[i](l, diff --git a/boxes/drawing.py b/boxes/drawing.py index 9dd76a9..4a6b3fc 100644 --- a/boxes/drawing.py +++ b/boxes/drawing.py @@ -718,7 +718,197 @@ showpage ) f.close() +class LBRN2Surface(Surface): + invert_y = False + dbg = False + + fonts = { + 'serif' : 'Times New Roman', + 'sans-serif' : 'Arial', + 'monospaced' : 'Courier New' + } + + def finish(self, inner_corners="loop"): + if self.dbg: print("LBRN2 save") + extents = self._adjust_coordinates() + w = extents.width * self.scale + h = extents.height * self.scale + + svg = ET.Element('LightBurnProject', AppVersion="1.0.06", FormatVersion="1", MaterialHeight="0", MirrorX="False", MirrorY="False") + svg.text = "\n" + num = 0 + txtOffset = {} + + tree = ET.ElementTree(svg) + if self.dbg: print ("8", num) + + for i, part in enumerate(self.parts): + if self.dbg: print ("7", num) + if not part.pathes: + continue + for j, path in enumerate(part.pathes): + Color = 2*int(path.params["rgb"][0])+4*int(path.params["rgb"][1])+int(path.params["rgb"][2]) + if Color == 4: # 4 is yellow in Lightburn + Color = 3 # use green instead + p = [] + x, y = 0, 0 + C = "" + start = None + last = None + path.faster_edges(inner_corners) + num = 0 + cnt = 1 + + ende = len(path.path)-1 + if self.dbg: + for c in path.path: + print ("6",num, c) + num += 1 + + num = 0 + c = path.path[num] + C, x, y = c[0:3] + if self.dbg: print("ende:" ,ende) + while num < ende or (C == "T" and num <= ende): #len(path.path): + if self.dbg: print ("0", num) + c = path.path[num] + if self.dbg: print("first: ", num, c) + + C, x, y = c[0:3] + if C == "M": + if self.dbg: print ("1", num) + sh = ET.SubElement(svg, "Shape", Type="Path", CutIndex=str(Color)) + sh.text = "\n " + sh.tail = "\n" + vl = ET.SubElement(sh, "VertList") + vl.text = f"V{x:.3f} {y:.3f}c0x1c1x1" + vl.tail = "\n" + pl = ET.SubElement(sh, "PrimList") + pl.text = ""#f"L{cnt} {cnt+1}" + pl.tail = "\n" + start = c + x0, y0 = x, y + # do something with M + done = False + bspline = False + while done == False and num < ende: #len(path.path): + num += 1 + c = path.path[num] + if self.dbg: print ("next: ",num, c) + C, x, y = c[0:3] + if C == "M": + if start and points_equal(start[1], start[2], x, y): + pl.text = "LineClosed" + start = c + cnt = 1 + if self.dbg: print ("next, because M") + done = True + elif C == "T": + if self.dbg: print ("next, because T") + done = True + else: + if C == "L": + vl.text+=(f"V{x:.3f} {y:.3f}c0x1c1x1") + pl.text += f"L{cnt-1} {cnt}" + cnt +=1 + elif C == "C": + x1, y1, x2, y2 = c[3:] + if self.dbg: print ("C: ",x0, y0, x1, y1, x, y, x2, y2) + vl.text+=(f"V{x0:.3f} {y0:.3f}c0x{(x1):.3f}c0y{(y1):.3f}c1x1V{x:.3f} {y:.3f}c0x1c1x{(x2):.3f}c1y{(y2):.3f}") + pl.text += f"L{cnt-1} {cnt}B{cnt} {cnt+1}" + cnt +=2 + bspline = True + else: + print("unknown", c) + if done == False: + x0, y0 = x, y + + if start and points_equal(start[1], start[2], x0, y0): + if bspline == False: + pl.text = "LineClosed" + start = c + if self.dbg: print ("2", num) + elif C == "T": + cnt = 1 + #C = "" + if self.dbg: print ("3", num) + m, text, params = c[3:] + m = m * Affine.translation(0, params['fs']) + if self.dbg: print ("T: ",x, y, c) + num += 1 + font, bold, italic = params['ff'] + if params.get('font', 'Arial')=='Arial': + f = self.fonts[font] + else: + f = params.get('font', 'Arial') + fontColor = 2*int(params['rgb'][0])+4*int(params['rgb'][1])+int(params['rgb'][2]) + if fontColor == 4: # 4 is yellow in Lightburn + fontColor = 3 # use green instead + + #alignment can be left|middle|end + if params.get('align', 'left')=='middle': + hor = '1' + else: + if params.get('align', 'left')=='end': + hor = '2' + else: + hor = '0' + ver = 2 # vertical is always bottom, text is shifted in box class + + pos = text.find('%') + offs = 0 + if pos >- 1: + if self.dbg: print ("p: ", pos, text[pos+1:pos+3]) + texttype = '2' + if self.dbg: print("l ", len(text[pos+1:pos+3])) + if text[pos+1:pos+2].isnumeric(): + if self.dbg: print ("t0", text[pos+1:pos+3]) + if text[pos+1:pos+3].isnumeric() and len(text[pos+1:pos+3]) == 2: + if self.dbg: print ("t1") + if text[pos:pos+3] in txtOffset: + if self.dbg: print ("t2") + offs = txtOffset[text[pos:pos+3]] + 1 + else: + if self.dbg: print ("t3") + offs = 0 + txtOffset[text[pos:pos+3]] = offs + else: + if self.dbg: print ("t4") + if text[pos:pos+2] in txtOffset: + if self.dbg: print ("t5") + offs = txtOffset[text[pos:pos+2]] + 1 + else: + offs = 0 + if self.dbg: print ("t6") + txtOffset[text[pos:pos+2]] = offs + else: + if self.dbg: print ("t7") + texttype = '0' + else: + texttype = '0' + if self.dbg: print ("t8") + if self.dbg: print ("o: ", text, txtOffset, offs) + + sh = ET.SubElement(svg, "Shape", Type="Text", CutIndex=str(fontColor), Font=f"{f}", H=f"{(params['fs']*1.75*0.6086434):.3f}", Str=f"{text}", Bold=f"{'1' if bold else '0'}", Italic=f"{'1' if italic else '0'}", Ah=f"{str(hor)}", Av=f"{str(ver)}", Eval=f"{texttype}", VariableOffset=f"{str(offs)}") # 1mm = 1.75 Lightburn H units + sh.text = "\n " + sh.tail = "\n" + xf = ET.SubElement(sh, "XForm") + xf.text = " ".join((f"{m[i]:.3f}" for i in (0, 3, 1, 4, 2, 5))) + xf.tail = "\n" + else: + if self.dbg: print ("4", num) + print ("next, because not M") + num += 1 + + url = self.metadata["url"].replace("&render=1", "") # remove render argument to get web form again + + pl = ET.SubElement(svg, "Notes", ShowOnLoad="1", Notes="File created by Boxes.py script, programmed by Florian Festi.\nLightburn output by Klaus Steinhammer.\n\nURL with settings:\n" + str(url)) + pl.text = "" + pl.tail = "\n" + + if self.dbg: print ("5", num) + tree.write(open(self._fname, "wb"), xml_declaration=True, method="xml", encoding="UTF-8") from random import random diff --git a/boxes/formats.py b/boxes/formats.py index 47303ca..d8ecab0 100644 --- a/boxes/formats.py +++ b/boxes/formats.py @@ -19,18 +19,19 @@ import subprocess import tempfile import os import shutil -from boxes.drawing import SVGSurface, PSSurface, Context +from boxes.drawing import SVGSurface, PSSurface, LBRN2Surface, Context class Formats: pstoedit_candidates = ["/usr/bin/pstoedit", "pstoedit", "pstoedit.exe"] - _BASE_FORMATS = ['svg', 'svg_Ponoko', 'ps'] + _BASE_FORMATS = ['svg', 'svg_Ponoko', 'ps', 'lbrn2'] formats = { "svg": None, "svg_Ponoko": None, "ps": None, + "lbrn2": None, "dxf": "-flat 0.1 -f dxf:-mm".split(), "gcode": "-f gcode".split(), "plt": "-f plot-hpgl".split(), @@ -42,6 +43,7 @@ class Formats: "svg": [('Content-type', 'image/svg+xml; charset=utf-8')], "svg_Ponoko": [('Content-type', 'image/svg+xml; charset=utf-8')], "ps": [('Content-type', 'application/postscript')], + "lbrn2": [('Content-type', 'application/lbrn2')], "dxf": [('Content-type', 'image/vnd.dxf')], "plt": [('Content-type', ' application/vnd.hp-hpgl')], "gcode": [('Content-type', 'text/plain; charset=utf-8')], @@ -64,7 +66,10 @@ class Formats: if fmt in ("svg", "svg_Ponoko"): surface = SVGSurface(filename) else: - surface = PSSurface(filename) + if fmt == "lbrn2": + surface = LBRN2Surface(filename) + else: + surface = PSSurface(filename) ctx = Context(surface) return surface, ctx