simetri.tikz.tikz
TikZ exporter. Draws shapes using the TikZ package for LaTeX. Sketch objects are converted to TikZ code.
1"""TikZ exporter. Draws shapes using the TikZ package for LaTeX. 2Sketch objects are converted to TikZ code.""" 3 4# This is a proof of concept. 5# To do: This whole module needs to be restructured. 6 7from __future__ import annotations 8 9from math import degrees, cos, sin, ceil 10from typing import List, Union 11from dataclasses import dataclass, field 12 13import numpy as np 14 15import simetri.graphics as sg 16from ..graphics.common import common_properties 17from ..graphics.all_enums import ( 18 BackStyle, 19 FontSize, 20 FontFamily, 21 MarkerType, 22 ShadeType, 23 Types, 24 TexLoc, 25 FrameShape, 26 DocumentClass, 27 Align, 28 ArrowLine, 29 BlendMode, 30 get_enum_value, 31 LineWidth, 32 LineDashArray, 33) 34from ..canvas.style_map import shape_style_map, line_style_map, marker_style_map 35from ..settings.settings import defaults, tikz_defaults 36from ..geometry.geometry import homogenize, close_points2, round_point 37from ..geometry.ellipse import ellipse_point 38from ..graphics.sketch import TagSketch, ShapeSketch 39from ..graphics.shape import Shape 40 41from ..colors.colors import Color 42 43 44np.set_printoptions(legacy="1.21") 45array = np.array 46 47 48enum_map = {} 49 50 51def scope_code_required(item: Union["Canvas", "Batch"]) -> bool: 52 """Check if a TikZ namespace is required for the item. 53 54 Args: 55 item (Union["Canvas", "Batch"]): The item to check. 56 57 Returns: 58 bool: True if a TikZ namespace is required, False otherwise. 59 """ 60 return ( 61 item.blend_mode is not None 62 or item.transparency_group 63 or (item.clip and item.mask) 64 ) 65 66 67@dataclass 68class Tex: 69 """Tex class for generating tex code. 70 71 Attributes: 72 begin_document (str): The beginning of the document. 73 end_document (str): The end of the document. 74 begin_tikz (str): The beginning of the TikZ environment. 75 end_tikz (str): The end of the TikZ environment. 76 packages (List[str]): List of required TeX packages. 77 tikz_libraries (List[str]): List of required TikZ libraries. 78 tikz_code (str): The generated TikZ code. 79 sketches (List["Sketch"]): List of Sketch objects. 80 """ 81 82 begin_document: str = defaults["begin_doc"] 83 end_document: str = defaults["end_doc"] 84 begin_tikz: str = defaults["begin_tikz"] 85 end_tikz: str = defaults["end_tikz"] 86 packages: List[str] = None 87 tikz_libraries: List[str] = None 88 tikz_code: str = "" # Generated by the canvas by using sketches 89 sketches: List["Sketch"] = field(default_factory=list) # List of TexSketch objects 90 91 def __post_init__(self): 92 """Post-initialization method.""" 93 self.type = Types.TEX 94 common_properties(self) 95 96 def tex_code(self, canvas: "Canvas", aux_code: str) -> str: 97 """Generate the final TeX code. 98 99 Args: 100 canvas ("Canvas"): The canvas object. 101 aux_code (str): Auxiliary code to include. 102 103 Returns: 104 str: The final TeX code. 105 """ 106 doc_code = [] 107 for sketch in self.sketches: 108 if sketch.location == TexLoc.DOCUMENT: 109 doc_code.append(sketch.code) 110 doc_code = "\n".join(doc_code) 111 back_color = f"\\pagecolor{color2tikz(canvas.back_color)}" 112 self.begin_document = self.begin_document + back_color + "\n" 113 if canvas.limits is not None: 114 begin_tikz = self.begin_tikz + get_limits_code(canvas) + "\n" 115 else: 116 begin_tikz = self.begin_tikz + "\n" 117 if scope_code_required(canvas): 118 scope = get_canvas_scope(canvas) 119 code = ( 120 self.get_preamble(canvas) 121 + self.begin_document 122 + doc_code 123 + begin_tikz 124 + scope 125 + self.get_tikz_code() 126 + aux_code 127 + "\\end{scope}\n" 128 + self.end_tikz 129 + self.end_document 130 ) 131 else: 132 code = ( 133 self.get_preamble(canvas) 134 + self.begin_document 135 + doc_code 136 + begin_tikz 137 + self.get_tikz_code() 138 + aux_code 139 + self.end_tikz 140 + self.end_document 141 ) 142 143 return code 144 145 def get_doc_class(self, border: float, font_size: int) -> str: 146 """Returns the document class. 147 148 Args: 149 border (float): The border size. 150 font_size (int): The font size. 151 152 Returns: 153 str: The document class string. 154 """ 155 return f"\\documentclass[{font_size}pt,tikz,border={border}pt]{{standalone}}\n" 156 157 def get_tikz_code(self) -> str: 158 """Returns the TikZ code. 159 160 Returns: 161 str: The TikZ code. 162 """ 163 code = "" 164 for sketch in self.sketches: 165 if sketch.location == TexLoc.PICTURE: 166 code += sketch.text + "\n" 167 168 return code 169 170 def get_tikz_libraries(self) -> str: 171 """Returns the TikZ libraries. 172 173 Returns: 174 str: The TikZ libraries string. 175 """ 176 return f"\\usetikzlibrary{{{','.join(self.tikz_libraries)}}}\n" 177 178 def get_packages(self, canvas) -> str: 179 """Returns the required TeX packages. 180 181 Args: 182 canvas: The canvas object. 183 184 Returns: 185 str: The required TeX packages. 186 """ 187 tikz_libraries = [] 188 tikz_packages = ['tikz', 'pgf'] 189 for page in canvas.pages: 190 for sketch in page.sketches: 191 if hasattr(sketch, "draw_frame") and sketch.draw_frame: 192 if hasattr(sketch, "frame_shape") and sketch.frame_shape != FrameShape.RECTANGLE: 193 if "shapes.geometric" not in tikz_libraries: 194 tikz_libraries.append("shapes.geometric") 195 if sketch.draw_markers: 196 if 'patterns' not in tikz_libraries: 197 tikz_libraries.append("patterns") 198 tikz_libraries.append("patterns.meta") 199 tikz_libraries.append("backgrounds") 200 tikz_libraries.append("shadings") 201 if sketch.line_dash_array: 202 if 'patterns' not in tikz_libraries: 203 tikz_libraries.append("patterns") 204 if sketch.subtype == Types.TAG_SKETCH: 205 if "fontspec" not in tikz_packages: 206 tikz_packages.append("fontspec") 207 else: 208 if hasattr(sketch, "marker_type") and sketch.marker_type == "indices": 209 if "fontspec" not in tikz_packages: 210 tikz_packages.append("fontspec") 211 if hasattr(sketch, 'back_style'): 212 if sketch.back_style == BackStyle.COLOR: 213 if "xcolor" not in tikz_packages: 214 tikz_packages.append("xcolor") 215 if sketch.back_style == BackStyle.SHADING: 216 if "shadings" not in tikz_libraries: 217 tikz_libraries.append("shadings") 218 if sketch.back_style == BackStyle.PATTERN: 219 if "patterns" not in tikz_libraries: 220 tikz_libraries.append("patterns") 221 tikz_libraries.append("patterns.meta") 222 223 224 return tikz_libraries, tikz_packages 225 226 def get_preamble(self, canvas) -> str: 227 """Returns the TeX preamble. 228 229 Args: 230 canvas: The canvas object. 231 232 Returns: 233 str: The TeX preamble. 234 """ 235 libraries, packages = self.get_packages(canvas) 236 237 if packages: 238 packages = f'\\usepackage{{{",".join(packages)}}}\n' 239 if 'fontspec' in packages: 240 fonts_section = f"""\\setmainfont{{{defaults['main_font']}}} 241\\setsansfont{{{defaults['sans_font']}}} 242\\setmonofont{{{defaults['mono_font']}}}\n""" 243 244 if libraries: 245 libraries = f'\\usetikzlibrary{{{",".join(libraries)}}}\n' 246 247 if canvas.border is None: 248 border = defaults["border"] 249 elif isinstance(canvas.border, (int, float)): 250 border = canvas.border 251 else: 252 raise ValueError("Canvas.border must be a positive numeric value.") 253 if border < 0: 254 raise ValueError("Canvas.border must be a positive numeric value.") 255 doc_class = self.get_doc_class(border, defaults["font_size"]) 256 # Check if different fonts are used 257 fonts_section = "" 258 fonts = canvas.get_fonts_list() 259 for font in fonts: 260 if font is None: 261 continue 262 font_family = font.replace(" ", "") 263 fonts_section += f"\\newfontfamily\\{font_family}[Scale=1.0]{{{font}}}\n" 264 preamble = f"{doc_class}{packages}{libraries}{fonts_section}" 265 266 indices = False 267 for sketch in canvas.active_page.sketches: 268 if hasattr(sketch, "marker_type") and sketch.marker_type == "indices": 269 indices = True 270 break 271 if indices: 272 font_family = defaults["indices_font_family"] 273 font_size = defaults["indices_font_size"] 274 count = 0 275 for sketch in canvas.active_page.sketches: 276 if hasattr(sketch, "marker_type") and sketch.marker_type == "indices": 277 preamble += "\\tikzset{\n" 278 node_style = ( 279 f"nodestyle{count}/.style={{draw, circle, gray, " 280 f"text=black, fill=white, line width = .5, inner sep=.5, " 281 f"font=\\{font_family}\\{font_size}}}\n}}\n" 282 ) 283 preamble += node_style 284 count += 1 285 for sketch in canvas.active_page.sketches: 286 if sketch.subtype == Types.TEX_SKETCH: 287 if sketch.location == TexLoc.PREAMBLE: 288 preamble += sketch.code + "\n" 289 return preamble 290 291 292def get_back_grid_code(grid: Grid, canvas: "Canvas") -> str: 293 """Page background grid code. 294 295 Args: 296 grid (Grid): The grid object. 297 canvas ("Canvas"): The canvas object. 298 299 Returns: 300 str: The background grid code. 301 """ 302 # \usetikzlibrary{backgrounds} 303 # \begin{scope}[on background layer] 304 # \fill[gray] (current bounding box.south west) rectangle 305 # (current bounding box.north east); 306 # \draw[white,step=.5cm] (current bounding box.south west) grid 307 # (current bounding box.north east); 308 # \end{scope} 309 grid = canvas.active_page.grid 310 back_color = color2tikz(grid.back_color) 311 line_color = color2tikz(grid.line_color) 312 step = grid.spacing 313 lines = ["\\begin{scope}[on background layer]\n"] 314 lines.append(f"\\fill[color={back_color}] (current bounding box.south west) ") 315 lines.append("rectangle (current bounding box.north east);\n") 316 options = [] 317 if grid.line_dash_array is not None: 318 options.append(f"dashed, dash pattern={get_dash_pattern(grid.line_dash_array)}") 319 if grid.line_width is not None: 320 options.append(f"line width={grid.line_width}") 321 if options: 322 options = ",".join(options) 323 lines.append(f"\\draw[color={line_color}, step={step}, {options}]") 324 else: 325 lines.append(f"\\draw[color={line_color},step={step}]") 326 lines.append("(current bounding box.south west)") 327 lines.append(" grid (current bounding box.north east);\n") 328 lines.append("\\end{scope}\n") 329 330 return "".join(lines) 331 332 333def get_limits_code(canvas: "Canvas") -> str: 334 """Get the limits of the canvas for clipping. 335 336 Args: 337 canvas ("Canvas"): The canvas object. 338 339 Returns: 340 str: The limits code for clipping. 341 """ 342 xmin, ymin, xmax, ymax = canvas.limits 343 points = [(xmin, ymin), (xmin, ymax), (xmax, ymax), (xmax, ymin)] 344 vertices = homogenize(points) @ canvas.xform_matrix 345 coords = " ".join([f"({v[0]}, {v[1]})" for v in vertices]) 346 347 return f"\\clip plot[] coordinates {{{coords}}};\n" 348 349 350def get_back_code(canvas: "Canvas") -> str: 351 """Get the background code for the canvas. 352 353 Args: 354 canvas ("Canvas"): The canvas object. 355 356 Returns: 357 str: The background code. 358 """ 359 back_color = color2tikz(canvas.back_color) 360 return f"\\pagecolor{back_color}\n" 361 362 363def get_tex_code(canvas: "Canvas") -> str: 364 """Convert the sketches in the Canvas to TikZ code. 365 366 Args: 367 canvas ("Canvas"): The canvas object. 368 369 Returns: 370 str: The TikZ code. 371 """ 372 373 def get_sketch_code(sketch, canvas, ind): 374 """Get the TikZ code for a sketch. 375 376 Args: 377 sketch: The sketch object. 378 canvas: The canvas object. 379 ind: The index. 380 381 Returns: 382 tuple: The TikZ code and the updated index. 383 """ 384 if sketch.subtype == Types.TAG_SKETCH: 385 code = draw_tag_sketch(sketch) 386 elif sketch.subtype == Types.BBOX_SKETCH: 387 code = draw_bbox_sketch(sketch) 388 elif sketch.subtype == Types.PATTERN_SKETCH: 389 code = draw_pattern_sketch(sketch) 390 elif sketch.subtype == Types.TEX_SKETCH: 391 if sketch.location == TexLoc.NONE: 392 code = sketch.code 393 else: 394 if sketch.draw_markers and sketch.marker_type == MarkerType.INDICES: 395 code = draw_shape_sketch(sketch, ind) 396 ind += 1 397 else: 398 code = draw_shape_sketch(sketch) 399 400 return code, ind 401 402 pages = canvas.pages 403 404 if pages: 405 for i, page in enumerate(pages): 406 sketches = page.sketches 407 back_color = f"\\pagecolor{color2tikz(page.back_color)}" 408 if i == 0: 409 code = [back_color] 410 else: 411 code.append(defaults["end_tikz"]) 412 code.append("\\newpage") 413 code.append(defaults["begin_tikz"]) 414 ind = 0 415 for sketch in sketches: 416 sketch_code, ind = get_sketch_code(sketch, canvas, ind) 417 code.append(sketch_code) 418 419 code = "\n".join(code) 420 else: 421 raise ValueError("No pages found in the canvas.") 422 return canvas.tex.tex_code(canvas, code) 423 424 425class Grid(sg.Shape): 426 """Grid shape. 427 428 Args: 429 p1: (x_min, y_min) 430 p2: (x_max, y_max) 431 dx: x step 432 dy: y step 433 """ 434 435 def __init__(self, p1, p2, dx, dy, **kwargs): 436 """ 437 Args: 438 p1: (x_min, y_min) 439 p2: (x_max, y_max) 440 dx: x step 441 dy: y step 442 """ 443 self.p1 = p1 444 self.p2 = p2 445 self.dx = dx 446 self.dy = dy 447 self.primary_points = sg.Points([p1, p2]) 448 self.closed = False 449 self.fill = False 450 self.stroke = True 451 self._b_box = None 452 super().__init__([p1, p2], xform_matrix=None, subtype=sg.Types.GRID, **kwargs) 453 454 455def get_min_size(sketch: ShapeSketch) -> str: 456 """Returns the minimum size of the tag node. 457 458 Args: 459 sketch (ShapeSketch): The shape sketch object. 460 461 Returns: 462 str: The minimum size of the tag node. 463 """ 464 options = [] 465 if sketch.frame_shape == "rectangle": 466 if sketch.frame_min_width is None: 467 width = defaults["min_width"] 468 else: 469 width = sketch.frame_min_width 470 if sketch.frame_min_height is None: 471 height = defaults["min_height"] 472 else: 473 height = sketch.frame_min_height 474 options.append(f"minimum width = {width}") 475 options.append(f"minimum height = {height}") 476 else: 477 if sketch.frame_min_size is None: 478 min_size = defaults["min_size"] 479 else: 480 min_size = sketch.frame_min_size 481 options.append(f"minimum size = {min_size}") 482 483 return options 484 485 486def frame_options(sketch: TagSketch) -> List[str]: 487 """Returns the options for the frame of the tag node. 488 489 Args: 490 sketch (TagSketch): The tag sketch object. 491 492 Returns: 493 List[str]: The options for the frame of the tag node. 494 """ 495 options = [] 496 if sketch.draw_frame: 497 options.append(sketch.frame_shape) 498 line_options = get_line_style_options(sketch, frame=True) 499 if line_options: 500 options.extend(line_options) 501 fill_options = get_fill_style_options(sketch, frame=True) 502 if fill_options: 503 options.extend(fill_options) 504 if sketch.text in [None, ""]: 505 min_size = get_min_size(sketch) 506 if min_size: 507 options.extend(min_size) 508 509 return options 510 511 512def color2tikz(color): 513 """Converts a Color object to a TikZ color string. 514 515 Args: 516 color (Color): The color object. 517 518 Returns: 519 str: The TikZ color string. 520 """ 521 # \usepackage{xcolor} 522 # \tikz\node[rounded corners, fill={rgb,255:red,21; green,66; blue,128}, 523 # text=white, draw=black] {hello world}; 524 # \definecolor{mycolor}{rgb}{1,0.2,0.3} 525 # \definecolor{mycolor}{R_g_b}{255,51,76} 526 # \definecolor{mypink1}{rgb}{0.858, 0.188, 0.478} 527 # \definecolor{mypink2}{R_g_b}{219, 48, 122} 528 # \definecolor{mypink3}{cmyk}{0, 0.7808, 0.4429, 0.1412} 529 # \definecolor{mygray}{gray}{0.6} 530 if color is None: 531 r, g, b, _ = 255, 255, 255, 255 532 return f"{{rgb,255:red,{r}; green,{g}; blue,{b}}}" 533 r, g, b = color.rgb255 534 535 return f"{{rgb,255:red,{r}; green,{g}; blue,{b}}}" 536 537 538def get_scope_options(item: Union["Canvas", "Sketch"]) -> str: 539 """Used for creating namespaces in TikZ. 540 541 Args: 542 item (Union["Canvas", "Sketch"]): The item to get scope options for. 543 544 Returns: 545 str: The scope options as a string. 546 """ 547 options = [] 548 549 if item.blend_group: 550 options.append(f"blend group={item.blend_mode}") 551 elif item.blend_mode: 552 options.append(f"blend mode={item.blend_mode}") 553 if item.fill_alpha not in [None, 1]: 554 options.append(f"fill opacity={item.fill_alpha}") 555 if item.line_alpha not in [None, 1]: 556 options.append(f"draw opacity={item.line_alpha}") 557 if item.text_alpha not in [None, 1]: 558 options.append(f"text opacity={item.alpha}") 559 if item.alpha not in [None, 1]: 560 options.append(f"opacity={item.alpha}") 561 if item.even_odd_rule: 562 options.append("even odd rule") 563 if item.transparency_group: 564 options.append("transparency group") 565 566 return ",".join(options) 567 568 569def get_clip_code(item: Union["Sketch", "Canvas"]) -> str: 570 """Returns the clip code for a sketch or Canvas. 571 572 Args: 573 item (Union["Sketch", "Canvas"]): The item to get clip code for. 574 575 Returns: 576 str: The clip code as a string. 577 """ 578 if item.mask.subtype == Types.CIRCLE: 579 x, y = item.mask.center[:2] 580 res = f"\\clip({x}, {y}) circle ({item.mask.radius});\n" 581 elif item.mask.subtype == Types.RECTANGLE: 582 x, y = item.mask.center[:2] 583 width, height = item.mask.width, item.mask.height 584 res = f"\\clip({x}, {y}) rectangle ({width}, {height});\n" 585 elif item.mask.subtype == Types.SHAPE: 586 # vertices = item.mask.primary_points.homogen_coords 587 # coords = " ".join([f"({v[0]}, {v[1]})" for v in vertices]) 588 # res = f"\\clip plot[] coordinates {{{coords}}};\n" 589 x, y = item.mask.origin[:2] 590 width, height = item.mask.width, item.mask.height 591 res = f"\\clip({x}, {y}) rectangle ({width}, {height});\n" 592 else: 593 res = "" 594 595 return res 596 597 598def get_canvas_scope(canvas): 599 """Returns the TikZ code for the canvas scope. 600 601 Args: 602 canvas: The canvas object. 603 604 Returns: 605 str: The TikZ code for the canvas scope. 606 """ 607 options = get_scope_options(canvas) 608 res = f"\\begin{{scope}}[{options}]\n" 609 if (canvas.clip and canvas.mask) or canvas.size is not None: 610 res += get_clip_code(canvas) 611 612 return res 613 614 615def draw_batch_sketch(sketch, canvas): 616 """Converts a BatchSketch to TikZ code. 617 618 Args: 619 sketch: The BatchSketch object. 620 canvas: The canvas object. 621 622 Returns: 623 str: The TikZ code for the BatchSketch. 624 """ 625 options = get_scope_options(sketch) 626 if options: 627 res = f"\\begin{{scope}}[{options}]\n" 628 else: 629 res = "" 630 if sketch.clip and sketch.mask: 631 res += get_clip_code(sketch) 632 for item in sketch.items: 633 if item.subtype in d_sketch_draw: 634 res += d_sketch_draw[item.subtype](item, canvas) 635 else: 636 raise ValueError(f"Sketch type {item.subtype} not supported.") 637 638 if sketch.clip and sketch.mask: 639 res += get_clip_code(sketch) 640 if options: 641 res += "\\end{scope}\n" 642 643 return res 644 645def draw_bbox_sketch(sketch): 646 """Converts a BBoxSketch to TikZ code. 647 648 Args: 649 sketch: The BBoxSketch object. 650 canvas: The canvas object. 651 652 Returns: 653 str: The TikZ code for the BBoxSketch. 654 """ 655 attrib_map = { 656 "line_color": "color", 657 "line_width": "line width", 658 "line_dash_array": "dash pattern", 659 } 660 attrib_list = ["line_color", "line_width", "line_dash_array"] 661 options = sg_to_tikz(sketch, attrib_list, attrib_map) 662 options = ", ".join(options) 663 res = f"\\draw[{options}]" 664 x1, y1 = sketch.vertices[1] 665 x2, y2 = sketch.vertices[3] 666 res += f"({x1}, {y1}) rectangle ({x2}, {y2});\n" 667 668 return res 669 670def draw_lace_sketch(item): 671 """Converts a LaceSketch to TikZ code. 672 673 Args: 674 item: The LaceSketch object. 675 676 Returns: 677 str: The TikZ code for the LaceSketch. 678 """ 679 if item.draw_fragments: 680 for fragment in item.fragments: 681 draw_shape_sketch(fragment) 682 if item.draw_plaits: 683 for plait in item.plaits: 684 plait.fill = True 685 draw_shape_sketch(plait) 686 687 688def get_draw(sketch): 689 """Returns the draw command for sketches. 690 691 Args: 692 sketch: The sketch object. 693 694 Returns: 695 str: The draw command as a string. 696 """ 697 # sketch.closed, sketch.fill, sketch.stroke, shading 698 decision_table = { 699 (True, True, True, True): "\\shadedraw", 700 (True, True, True, False): "\\filldraw", 701 (True, True, False, True): "\\shade", 702 (True, True, False, False): "\\fill", 703 (True, False, True, True): "\\draw", 704 (True, False, True, False): "\\draw", 705 (True, False, False, True): False, 706 (True, False, False, False): False, 707 (False, True, True, True): "\\draw", 708 (False, True, True, False): "\\draw", 709 (False, True, False, True): False, 710 (False, True, False, False): False, 711 (False, False, True, True): "\\draw", 712 (False, False, True, False): "\\draw", 713 (False, False, False, True): False, 714 (False, False, False, False): False, 715 } 716 if sketch.markers_only: 717 res = "\\draw" 718 else: 719 if hasattr(sketch, 'back_style'): 720 shading = sketch.back_style == BackStyle.SHADING 721 else: 722 shading = False 723 if not hasattr(sketch, 'closed'): 724 closed = False 725 else: 726 closed = sketch.closed 727 if not hasattr(sketch, 'fill'): 728 fill = False 729 else: 730 fill = sketch.fill 731 if not hasattr(sketch, 'stroke'): 732 stroke = False 733 else: 734 stroke = sketch.stroke 735 736 res = decision_table[(closed, fill, stroke, shading)] 737 738 return res 739 740 741def get_frame_options(sketch): 742 """Returns the options for the frame of a TagSketch. 743 744 Args: 745 sketch: The TagSketch object. 746 747 Returns: 748 list: The options for the frame of the TagSketch. 749 """ 750 options = get_line_style_options(sketch) 751 options += get_fill_style_options(sketch) 752 if sketch.text in [None, ""]: 753 if sketch.frame.frame_shape == "rectangle": 754 width = sketch.frame.min_width 755 height = sketch.frame.min_height 756 if not width: 757 width = defaults["min_width"] 758 if not height: 759 height = defaults["min_height"] 760 options += "minimum width = {width}, minimum height = {height}" 761 else: 762 size = sketch.frame.min_size 763 if not size: 764 size = defaults["min_size"] 765 options += f"minimum size = {size}" 766 return options 767 768 769def draw_tag_sketch(sketch): 770 """Converts a TagSketch to TikZ code. 771 772 Args: 773 sketch: The TagSketch object. 774 canvas: The canvas object. 775 776 Returns: 777 str: The TikZ code for the TagSketch. 778 """ 779 # \node at (0,0) {some text}; 780 def get_font_family(sketch): 781 default_fonts = [defaults['main_font'], defaults['sans_font'], defaults['mono_font']] 782 783 if sketch.font_family in default_fonts: 784 if sketch.font_family == defaults['main_font']: 785 res = 'tex_family', '' 786 elif sketch.font_family == defaults['sans_font']: 787 res = 'tex_family', 'textsf' 788 else: # defaults['mono_font'] 789 res = 'tex_family', 'texttt' 790 elif sketch.font_family: 791 if isinstance(sketch.font_family, FontFamily): 792 if sketch.font_family == FontFamily.SANSSERIF: 793 res = 'tex_family', 'textsf' 794 elif sketch.font_family == FontFamily.MONOSPACE: 795 res = 'tex_family', 'texttt' 796 else: 797 res = 'tex_family', 'textrm' 798 799 elif isinstance(sketch.font_family, str): 800 res = 'new_family', sketch.font_family.replace(" ", "") 801 802 else: 803 raise ValueError(f"Font family {sketch.font_family} not supported.") 804 else: 805 res = 'no_family', None 806 807 return res 808 809 def get_font_size(sketch): 810 811 if sketch.font_size: 812 if isinstance(sketch.font_size, FontSize): 813 res = 'tex_size', sketch.font_size.value 814 else: 815 res = 'num_size', sketch.font_size 816 else: 817 res = 'no_size', None 818 819 return res 820 821 res = [] 822 x, y = sketch.pos[:2] 823 824 options = "" 825 if sketch.draw_frame: 826 options += "draw" 827 if sketch.stroke: 828 if sketch.frame_shape != FrameShape.RECTANGLE: 829 options += f", {sketch.frame_shape}, " 830 line_style_options = get_line_style_options(sketch) 831 if line_style_options: 832 options += ', ' + ', '.join(line_style_options) 833 if sketch.frame_inner_sep: 834 options += f", inner sep={sketch.frame_inner_sep}" 835 if sketch.minimum_width: 836 options += f", minimum width={sketch.minimum_width}" 837 if sketch.smooth and sketch.frame_shape not in [ 838 FrameShape.CIRCLE, 839 FrameShape.ELLIPSE, 840 ]: 841 options += ", smooth" 842 843 if sketch.fill and sketch.back_color: 844 options += f", fill={color2tikz(sketch.frame_back_color)}" 845 if sketch.anchor: 846 options += f", anchor={sketch.anchor.value}" 847 if sketch.back_style == BackStyle.SHADING and sketch.fill: 848 shading_options = get_shading_options(sketch)[0] 849 options += ", " + shading_options 850 if sketch.back_style == BackStyle.PATTERN and sketch.fill: 851 pattern_options = get_pattern_options(sketch)[0] 852 options += ", " + pattern_options 853 if sketch.align != defaults["tag_align"]: 854 options += f", align={sketch.align.value}" 855 if sketch.text_width: 856 options += f", text width={sketch.text_width}" 857 858 859# no_family, tex_family, new_family 860# no_size, tex_size, num_size 861 862# num_size and new_family {\fontsize{20}{24} \selectfont \Verdana ABCDEFG Hello, World! 25} 863# tex_size and new_family {\large{\selectfont \Verdana ABCDEFG Hello, World! 50}} 864# no_size and new_family {\selectfont \Verdana ABCDEFG Hello, World! 50} 865 866# tex_family {\textsc{\textit{\textbf{\Huge{\texttt{ABCDG Just a test -50}}}}}}; 867 868# no_family {\textsc{\textit{\textbf{\Huge{ABCDG Just a test -50}}}}}; 869 870 if sketch.font_color is not None and sketch.font_color != defaults["font_color"]: 871 options += f", text={color2tikz(sketch.font_color)}" 872 family, font_family = get_font_family(sketch) 873 size, font_size = get_font_size(sketch) 874 tex_text = '' 875 if sketch.small_caps: 876 tex_text += '\\textsc{' 877 878 if sketch.italic: 879 tex_text += '\\textit{' 880 881 if sketch.bold: 882 tex_text += '\\textbf{' 883 884 if size == 'num_size': 885 f_size = font_size 886 f_size2 = ceil(font_size * 1.2) 887 tex_text += f"\\fontsize{{{f_size}}}{{{f_size2}}}\\selectfont " 888 889 elif size == 'tex_size': 890 tex_text += f"\\{font_size}{{\\selectfont " 891 892 else: 893 tex_text += "\\selectfont " 894 895 if family == 'new_family': 896 tex_text += f"\\{font_family} {sketch.text}}}" 897 898 elif family == 'tex_family': 899 if font_family: 900 tex_text += f"\\{font_family}{{ {sketch.text}}}}}" 901 else: 902 tex_text += f"{{ {sketch.text}}}" 903 else: # no_family 904 tex_text += f"{{ {sketch.text}}}" 905 906 tex_text = '{' + tex_text 907 908 open_braces = tex_text.count('{') 909 close_braces = tex_text.count('}') 910 tex_text = tex_text + '}' * (open_braces - close_braces) 911 912 res.append(f"\\node[{options}] at ({x}, {y}) {tex_text};\n") 913 914 return ''.join(res) 915 916 917def get_dash_pattern(line_dash_array): 918 """Returns the dash pattern for a line. 919 920 Args: 921 line_dash_array: The dash array for the line. 922 923 Returns: 924 str: The dash pattern as a string. 925 """ 926 dash_pattern = [] 927 for i, dash in enumerate(line_dash_array): 928 if i % 2 == 0: 929 dash_pattern.extend(["on", f"{dash}pt"]) 930 else: 931 dash_pattern.extend(["off", f"{dash}pt"]) 932 933 return " ".join(dash_pattern) 934 935 936def sg_to_tikz(sketch, attrib_list, attrib_map, conditions=None, exceptions=None): 937 """Converts the attributes of a sketch to TikZ options. 938 939 Args: 940 sketch: The sketch object. 941 attrib_list: The list of attributes to convert. 942 attrib_map: The map of attributes to TikZ options. 943 conditions: Optional conditions for the attributes. 944 exceptions: Optional exceptions for the attributes. 945 946 Returns: 947 list: The TikZ options as a list. 948 """ 949 skip = ["marker_color", "fill_color"] 950 tikz_way = {'line_width':LineWidth, 'line_dash_array':LineDashArray} 951 if exceptions: 952 skip += exceptions 953 d_converters = { 954 "line_color": color2tikz, 955 "fill_color": color2tikz, 956 "draw": color2tikz, 957 "line_dash_array": get_dash_pattern, 958 } 959 options = [] 960 for attrib in attrib_list: 961 if attrib not in attrib_map: 962 continue 963 if conditions and attrib in conditions and not conditions[attrib]: 964 continue 965 if attrib in tikz_way: 966 value = getattr(sketch, attrib) 967 if isinstance(value, tikz_way[attrib]): 968 option = value.value 969 options.append(option) 970 continue 971 if isinstance(value, str): 972 if value in tikz_way[attrib]: 973 options.append(value) 974 continue 975 976 tikz_attrib = attrib_map[attrib] 977 if hasattr(sketch, attrib): 978 value = getattr(sketch, attrib) 979 if value is not None and tikz_attrib in list(attrib_map.values()): 980 if attrib in ['smooth']: # smooth is a boolean 981 if value: 982 options.append("smooth") 983 984 elif attrib in skip: 985 value = color2tikz(getattr(sketch, attrib)) 986 options.append(f"{tikz_attrib}={value}") 987 988 elif value != tikz_defaults[tikz_attrib]: 989 if attrib in d_converters: 990 value = d_converters[attrib](value) 991 options.append(f"{tikz_attrib}={value}") 992 993 return options 994 995 996def get_line_style_options(sketch, exceptions=None): 997 """Returns the options for the line style. 998 999 Args: 1000 sketch: The sketch object. 1001 exceptions: Optional exceptions for the line style options. 1002 1003 Returns: 1004 list: The line style options as a list. 1005 """ 1006 attrib_map = { 1007 "line_color": "color", 1008 "line_width": "line width", 1009 "line_dash_array": "dash pattern", 1010 "line_cap": "line cap", 1011 "line_join": "line join", 1012 "line_miter_limit": "miter limit", 1013 "line_dash_phase": "dash phase", 1014 "line_alpha": "draw opacity", 1015 "smooth": "smooth", 1016 "fillet_radius": "rounded corners", 1017 } 1018 attribs = list(line_style_map.keys()) 1019 if sketch.stroke: 1020 if exceptions and "draw_fillets" not in exceptions: 1021 conditions = {"fillet_radius": sketch.draw_fillets} 1022 else: 1023 conditions = None 1024 if sketch.line_alpha not in [None, 1]: 1025 sketch.line_alpha = sketch.line_alpha 1026 elif sketch.alpha not in [None, 1]: 1027 sketch.fill_alpha = sketch.alpha 1028 else: 1029 attribs.remove("line_alpha") 1030 if not sketch.smooth: 1031 attribs.remove("smooth") 1032 res = sg_to_tikz(sketch, attribs, attrib_map, conditions, exceptions) 1033 else: 1034 res = [] 1035 1036 return res 1037 1038 1039def get_fill_style_options(sketch, exceptions=None, frame=False): 1040 """Returns the options for the fill style. 1041 1042 Args: 1043 sketch: The sketch object. 1044 exceptions: Optional exceptions for the fill style options. 1045 frame: Optional flag for frame fill style. 1046 1047 Returns: 1048 list: The fill style options as a list. 1049 """ 1050 attrib_map = { 1051 "fill_color": "fill", 1052 "fill_alpha": "fill opacity", 1053 #'fill_mode': 'even odd rule', 1054 "blend_mode": "blend mode", 1055 "frame_back_color": "fill", 1056 } 1057 attribs = list(shape_style_map.keys()) 1058 if sketch.fill_alpha not in [None, 1]: 1059 sketch.fill_alpha = sketch.fill_alpha 1060 elif sketch.alpha not in [None, 1]: 1061 sketch.fill_alpha = sketch.alpha 1062 else: 1063 attribs.remove("fill_alpha") 1064 if sketch.fill and not sketch.back_style == BackStyle.PATTERN: 1065 res = sg_to_tikz(sketch, attribs, attrib_map, exceptions=exceptions) 1066 if frame: 1067 res = [f"fill = {color2tikz(getattr(sketch, 'frame_back_color'))}"] + res 1068 else: 1069 res = [] 1070 1071 return res 1072 1073 1074def get_axis_shading_colors(sketch): 1075 """Returns the shading colors for the axis. 1076 1077 Args: 1078 sketch: The sketch object. 1079 1080 Returns: 1081 str: The shading colors for the axis. 1082 """ 1083 def get_color(color, color_key): 1084 if isinstance(color, Color): 1085 res = color2tikz(color) 1086 else: 1087 res = defaults[color_key] 1088 1089 return res 1090 1091 left = get_color(sketch.shade_left_color, "shade_left_color") 1092 right = get_color(sketch.shade_right_color, "shade_right_color") 1093 top = get_color(sketch.shade_top_color, "shade_top_color") 1094 bottom = get_color(sketch.shade_bottom_color, "shade_bottom_color") 1095 middle = get_color(sketch.shade_middle_color, "shade_middle_color") 1096 1097 axis_colors = { 1098 ShadeType.AXIS_BOTTOM_MIDDLE: f"bottom color={bottom}, middle color={middle}", 1099 ShadeType.AXIS_LEFT_MIDDLE: f"left color={left}, middle color={middle}", 1100 ShadeType.AXIS_RIGHT_MIDDLE: f"right color={right}, middle color={middle}", 1101 ShadeType.AXIS_TOP_MIDDLE: f"top color={top}, middle color={middle}", 1102 ShadeType.AXIS_LEFT_RIGHT: f"left color={left}, right color={right}", 1103 ShadeType.AXIS_TOP_BOTTOM: f"top color={top}, bottom color={bottom}", 1104 } 1105 1106 res = axis_colors[sketch.shade_type] 1107 return res 1108 1109 1110def get_bilinear_shading_colors(sketch): 1111 """Returns the shading colors for the bilinear shading. 1112 1113 Args: 1114 sketch: The sketch object. 1115 1116 Returns: 1117 str: The shading colors for the bilinear shading. 1118 """ 1119 res = [] 1120 if sketch.shade_upper_left_color: 1121 res.append(f"upper left = {color2tikz(sketch.shade_upper_left_color)}") 1122 if sketch.shade_upper_right_color: 1123 res.append(f"upper right = {color2tikz(sketch.shade_upper_right_color)}") 1124 if sketch.shade_lower_left_color: 1125 res.append(f"lower left = {color2tikz(sketch.shade_lower_left_color)}") 1126 if sketch.shade_lower_right_color: 1127 res.append(f"lower right = {color2tikz(sketch.shade_lower_right_color)}") 1128 1129 return ", ".join(res) 1130 1131 1132def get_radial_shading_colors(sketch): 1133 """Returns the shading colors for the radial shading. 1134 1135 Args: 1136 sketch: The sketch object. 1137 1138 Returns: 1139 str: The shading colors for the radial shading. 1140 """ 1141 res = [] 1142 if sketch.shade_type == ShadeType.RADIAL_INNER: 1143 res.append(f"inner color = {color2tikz(sketch.shade_inner_color)}") 1144 elif sketch.shade_type == ShadeType.RADIAL_OUTER: 1145 res.append(f"outer color = {color2tikz(sketch.shade_outer_color)}") 1146 elif sketch.shade_type == ShadeType.RADIAL_INNER_OUTER: 1147 res.append(f"inner color = {color2tikz(sketch.shade_inner_color)}") 1148 res.append(f"outer color = {color2tikz(sketch.shade_outer_color)}") 1149 1150 return ", ".join(res) 1151 1152 1153axis_shading_types = [ 1154 ShadeType.AXIS_BOTTOM_MIDDLE, 1155 ShadeType.AXIS_LEFT_MIDDLE, 1156 ShadeType.AXIS_RIGHT_MIDDLE, 1157 ShadeType.AXIS_TOP_MIDDLE, 1158 ShadeType.AXIS_LEFT_RIGHT, 1159 ShadeType.AXIS_TOP_BOTTOM, 1160] 1161 1162radial_shading_types = [ 1163 ShadeType.RADIAL_INNER, 1164 ShadeType.RADIAL_OUTER, 1165 ShadeType.RADIAL_INNER_OUTER, 1166] 1167 1168 1169def get_shading_options(sketch): 1170 """Returns the options for the shading. 1171 1172 Args: 1173 sketch: The sketch object. 1174 1175 Returns: 1176 list: The shading options as a list. 1177 """ 1178 shade_type = sketch.shade_type 1179 if shade_type in axis_shading_types: 1180 res = get_axis_shading_colors(sketch) 1181 if sketch.shade_axis_angle: 1182 res += f", shading angle={sketch.shade_axis_angle}" 1183 elif shade_type == ShadeType.BILINEAR: 1184 res = get_bilinear_shading_colors(sketch) 1185 elif shade_type in radial_shading_types: 1186 res = get_radial_shading_colors(sketch) 1187 elif shade_type == ShadeType.BALL: 1188 res = f"ball color = {color2tikz(sketch.shade_ball_color)}" 1189 elif shade_type == ShadeType.COLORWHEEL: 1190 res = "shading=color wheel" 1191 elif shade_type == ShadeType.COLORWHEEL_BLACK: 1192 res = "shading=color wheel black center" 1193 elif shade_type == ShadeType.COLORWHEEL_WHITE: 1194 res = "shading=color wheel white center" 1195 1196 return [res] 1197 1198 1199def get_pattern_options(sketch): 1200 """Returns the options for the patterns. 1201 1202 Args: 1203 sketch: The sketch object. 1204 1205 Returns: 1206 list: The pattern options as a list. 1207 """ 1208 pattern_type = sketch.pattern_type 1209 if pattern_type: 1210 distance = sketch.pattern_distance 1211 options = f"pattern={{{pattern_type}[distance={distance}, " 1212 angle = degrees(sketch.pattern_angle) 1213 if angle: 1214 options += f"angle={angle}, " 1215 line_width = sketch.pattern_line_width 1216 if line_width: 1217 options += f"line width={line_width}, " 1218 x_shift = sketch.pattern_x_shift 1219 if x_shift: 1220 options += f"xshift={x_shift}, " 1221 y_shift = sketch.pattern_y_shift 1222 if y_shift: 1223 options += f"yshift={y_shift}, " 1224 if pattern_type in ["Stars", "Dots"]: 1225 radius = sketch.pattern_radius 1226 if radius: 1227 options += f"radius={radius}, " 1228 if pattern_type == "Stars": 1229 points = sketch.pattern_points 1230 if points: 1231 options += f"points={points}, " 1232 options = options.strip() 1233 if options.endswith(","): 1234 options = options[:-1] 1235 options += "]" 1236 color = sketch.pattern_color 1237 if color and color != sg.black: 1238 options += f", pattern color={color2tikz(color)}, " 1239 1240 options += "}" 1241 res = [options] 1242 else: 1243 res = [] 1244 1245 return res 1246 1247 1248def get_marker_options(sketch): 1249 """Returns the options for the markers. 1250 1251 Args: 1252 sketch: The sketch object. 1253 1254 Returns: 1255 list: The marker options as a list. 1256 """ 1257 attrib_map = { 1258 # 'marker': 'mark', 1259 "marker_size": "mark size", 1260 "marker_angle": "rotate", 1261 # 'fill_color': 'color', 1262 "marker_color": "color", 1263 "marker_fill": "fill", 1264 "marker_opacity": "opacity", 1265 "marker_repeat": "mark repeat", 1266 "marker_phase": "mark phase", 1267 "marker_tension": "tension", 1268 "marker_line_width": "line width", 1269 "marker_line_style": "style", 1270 # 'line_color': 'line color', 1271 } 1272 # if mark_stroke is false make line color same as fill color 1273 if sketch.draw_markers: 1274 res = sg_to_tikz(sketch, marker_style_map.keys(), attrib_map) 1275 else: 1276 res = [] 1277 1278 return res 1279 1280 1281def draw_shape_sketch_with_indices(sketch, ind): 1282 """Draws a shape sketch with circle markers with index numbers in them. 1283 1284 Args: 1285 sketch: The shape sketch object. 1286 ind: The index. 1287 1288 Returns: 1289 str: The TikZ code for the shape sketch with indices. 1290 """ 1291 begin_scope = get_begin_scope(ind) 1292 body = get_draw(sketch) 1293 if body: 1294 options = get_line_style_options(sketch) 1295 if sketch.fill and sketch.closed: 1296 options += get_fill_style_options(sketch) 1297 if sketch.smooth: 1298 if sketch.closed: 1299 options += ["smooth cycle"] 1300 else: 1301 options += ["smooth"] 1302 options = ", ".join(options) 1303 body += f"[{options}]" 1304 else: 1305 body = "" 1306 vertices = sketch.vertices 1307 vertices = [str(x) for x in vertices] 1308 str_lines = [vertices[0] + "node{0}"] 1309 n = len(vertices) 1310 for i, vertice in enumerate(vertices[1:]): 1311 if (i + 1) % 6 == 0: 1312 if i == n - 1: 1313 str_lines.append(f" -- {vertice} node{{{i+1}}}\n") 1314 else: 1315 str_lines.append(f"\n\t-- {vertice} node{{{i+1}}}") 1316 else: 1317 str_lines.append(f"-- {vertice} node{{{i+1}}}") 1318 if body: 1319 if sketch.closed: 1320 str_lines.append(" -- cycle;\n") 1321 str_lines.append(";\n") 1322 end_scope = get_end_scope() 1323 1324 return begin_scope + body + "".join(str_lines) + end_scope 1325 1326 1327def draw_shape_sketch_with_markers(sketch): 1328 """Draws a shape sketch with markers. 1329 1330 Args: 1331 sketch: The shape sketch object. 1332 1333 Returns: 1334 str: The TikZ code for the shape sketch with markers. 1335 """ 1336 # begin_scope = get_begin_scope() 1337 body = get_draw(sketch) 1338 if body: 1339 options = get_line_style_options(sketch) 1340 if sketch.fill and sketch.closed: 1341 options += get_fill_style_options(sketch) 1342 if sketch.smooth and sketch.closed: 1343 options += ["smooth cycle"] 1344 elif sketch.smooth: 1345 options += ["smooth"] 1346 options = ", ".join(options) 1347 if options: 1348 body += f"[{options}]" 1349 else: 1350 body = "" 1351 1352 if sketch.draw_markers: 1353 marker_options = ", ".join(get_marker_options(sketch)) 1354 else: 1355 marker_options = "" 1356 1357 vertices = [str(x) for x in sketch.vertices] 1358 1359 str_lines = [vertices[0]] 1360 for i, vertice in enumerate(vertices[1:]): 1361 if (i + 1) % 6 == 0: 1362 str_lines.append(f"\n\t{vertice} ") 1363 else: 1364 str_lines.append(f" {vertice} ") 1365 coordinates = "".join(str_lines) 1366 1367 marker = get_enum_value(MarkerType, sketch.marker_type) 1368 # marker = sketch.marker_type.value 1369 if sketch.markers_only: 1370 markers_only = "only marks ," 1371 else: 1372 markers_only = "" 1373 if sketch.draw_markers and marker_options: 1374 body += ( 1375 f" plot[mark = {marker}, {markers_only}mark options = {{{marker_options}}}] " 1376 f"\ncoordinates {{{coordinates}}};\n" 1377 ) 1378 elif sketch.draw_markers: 1379 body += ( 1380 f" plot[mark = {marker}, {markers_only}] coordinates {{{coordinates}}};\n" 1381 ) 1382 else: 1383 body += f" plot[tension=.5] coordinates {{{coordinates}}};\n" 1384 1385 return body 1386 1387 1388def get_begin_scope(ind=None): 1389 """Returns \begin{scope}[every node/.append style=nodestyle{ind}]. 1390 1391 Args: 1392 ind: Optional index for the scope. 1393 1394 Returns: 1395 str: The begin scope string. 1396 """ 1397 if ind is None: 1398 res = "\\begin{scope}[]\n" 1399 else: 1400 res = f"\\begin{{scope}}[every node/.append style=nodestyle{ind}]\n" 1401 1402 return res 1403 1404 1405def get_end_scope(): 1406 """Returns \\end{scope}. 1407 1408 Returns: 1409 str: The end scope string. 1410 """ 1411 return "\\end{scope}\n" 1412 1413def draw_pattern_sketch(sketch): 1414 """Draws a pattern sketch. 1415 1416 Args: 1417 sketch: The pattern sketch object. 1418 1419 Returns: 1420 str: The TikZ code for the pattern sketch. 1421 """ 1422 begin_scope = "\\begin{scope}" 1423 1424 options = [] 1425 1426 if sketch.back_style == BackStyle.PATTERN and sketch.fill and sketch.closed: 1427 options += get_pattern_options(sketch) 1428 if sketch.stroke: 1429 options += get_line_style_options(sketch) 1430 if sketch.closed and sketch.fill: 1431 options += get_fill_style_options(sketch) 1432 if sketch.smooth: 1433 options += ["smooth"] 1434 if sketch.back_style == BackStyle.SHADING and sketch.fill and sketch.closed: 1435 options += get_shading_options(sketch) 1436 options = ", ".join(options) 1437 if options: 1438 begin_scope += f"[{options}]\n" 1439 end_scope = get_end_scope() 1440 1441 pattern = sketch.pattern 1442 draw = get_draw(sketch) 1443 if not draw: 1444 return "" 1445 all_vertices = sketch.kernel_vertices @ sketch.all_matrices 1446 vertices_list = np.hsplit(all_vertices, sketch.count) 1447 shapes = [] 1448 for vertices in vertices_list: 1449 vertices @= sketch.xform_matrix 1450 vertices = [tuple(vert) for vert in vertices[:,:2].tolist()] 1451 n = len(vertices) 1452 str_lines = [f"{vertices[0]}"] 1453 for i, vertice in enumerate(vertices[1:]): 1454 if (i + 1) % 8 == 0: 1455 if i == n - 1: 1456 str_lines.append(f"-- {vertice} \n") 1457 else: 1458 str_lines.append(f"\n\t-- {vertice} ") 1459 else: 1460 str_lines.append(f"-- {vertice} ") 1461 if sketch.closed: 1462 str_lines.append("-- cycle;\n") 1463 else: 1464 str_lines.append(";\n") 1465 shapes.append(draw + "".join(str_lines)) 1466 1467 1468 return begin_scope + f"[{options}]\n" + "\n".join(shapes) + end_scope 1469 1470 1471def draw_sketch(sketch): 1472 """Draws a plain shape sketch. 1473 1474 Args: 1475 sketch: The shape sketch object. 1476 1477 Returns: 1478 str: The TikZ code for the plain shape sketch. 1479 """ 1480 res = get_draw(sketch) 1481 if not res: 1482 return "" 1483 options = [] 1484 1485 if sketch.back_style == BackStyle.PATTERN and sketch.fill and sketch.closed: 1486 options += get_pattern_options(sketch) 1487 if sketch.stroke: 1488 options += get_line_style_options(sketch) 1489 if sketch.closed and sketch.fill: 1490 options += get_fill_style_options(sketch) 1491 if sketch.smooth: 1492 options += ["smooth"] 1493 if sketch.back_style == BackStyle.SHADING and sketch.fill and sketch.closed: 1494 options += get_shading_options(sketch) 1495 options = ", ".join(options) 1496 if options: 1497 res += f"[{options}]" 1498 vertices = sketch.vertices 1499 n = len(vertices) 1500 str_lines = [f"{vertices[0]}"] 1501 for i, vertice in enumerate(vertices[1:]): 1502 if (i + 1) % 8 == 0: 1503 if i == n - 1: 1504 str_lines.append(f"-- {vertice} \n") 1505 else: 1506 str_lines.append(f"\n\t-- {vertice} ") 1507 else: 1508 str_lines.append(f"-- {vertice} ") 1509 if sketch.closed: 1510 str_lines.append("-- cycle;\n") 1511 else: 1512 str_lines.append(";\n") 1513 if res: 1514 res += "".join(str_lines) 1515 else: 1516 res = "".join(str_lines) 1517 return res 1518 1519 1520def draw_tex_sketch(sketch): 1521 """Draws a TeX sketch. 1522 1523 Args: 1524 sketch: The TeX sketch object. 1525 1526 Returns: 1527 str: The TeX code for the TeX sketch. 1528 """ 1529 1530 return sketch.code 1531 1532def draw_shape_sketch(sketch, ind=None): 1533 """Draws a shape sketch. 1534 1535 Args: 1536 sketch: The shape sketch object. 1537 ind: Optional index for the shape sketch. 1538 1539 Returns: 1540 str: The TikZ code for the shape sketch. 1541 """ 1542 d_subtype_draw = { 1543 sg.Types.ARC_SKETCH: draw_arc_sketch, 1544 sg.Types.BEZIER_SKETCH: draw_bezier_sketch, 1545 sg.Types.CIRCLE_SKETCH: draw_circle_sketch, 1546 sg.Types.ELLIPSE_SKETCH: draw_ellipse_sketch, 1547 sg.Types.LINE_SKETCH: draw_line_sketch, 1548 } 1549 if sketch.subtype in d_subtype_draw: 1550 res = d_subtype_draw[sketch.subtype](sketch) 1551 elif sketch.draw_markers and sketch.marker_type == MarkerType.INDICES: 1552 res = draw_shape_sketch_with_indices(sketch, ind) 1553 elif sketch.draw_markers or sketch.smooth: 1554 res = draw_shape_sketch_with_markers(sketch) 1555 else: 1556 res = draw_sketch(sketch) 1557 1558 return res 1559 1560def draw_line_sketch(sketch): 1561 """Draws a line sketch. 1562 1563 Args: 1564 sketch: The line sketch object. 1565 1566 Returns: 1567 str: The TikZ code for the line sketch. 1568 """ 1569 begin_scope = get_begin_scope() 1570 res = "\\draw" 1571 exceptions = ["draw_fillets", "fillet_radius", "line_join", "line_miter_limit"] 1572 options = get_line_style_options(sketch, exceptions=exceptions) 1573 1574 start = sketch.vertices[0] 1575 end = sketch.vertices[1] 1576 options = ", ".join(options) 1577 res += f"[{options}]" 1578 res += f" {start[:2]} -- {end[:2]};\n" 1579 end_scope = get_end_scope() 1580 1581 return begin_scope + res + end_scope 1582 1583 1584def draw_circle_sketch(sketch): 1585 """Draws a circle sketch. 1586 1587 Args: 1588 sketch: The circle sketch object. 1589 1590 Returns: 1591 str: The TikZ code for the circle sketch. 1592 """ 1593 begin_scope = get_begin_scope() 1594 res = get_draw(sketch) 1595 if not res: 1596 return "" 1597 options = get_line_style_options(sketch) 1598 fill_options = get_fill_style_options(sketch) 1599 options += fill_options 1600 if sketch.smooth: 1601 options += ["smooth"] 1602 options = ", ".join(options) 1603 if options: 1604 res += f"[{options}]" 1605 x, y = sketch.center[:2] 1606 res += f"({x}, {y}) circle ({sketch.radius});\n" 1607 end_scope = get_end_scope() 1608 1609 return begin_scope + res + end_scope 1610 1611 1612def draw_rect_sketch(sketch): 1613 """Draws a rectangle sketch. 1614 1615 Args: 1616 sketch: The rectangle sketch object. 1617 1618 Returns: 1619 str: The TikZ code for the rectangle sketch. 1620 """ 1621 begin_scope = get_begin_scope() 1622 res = get_draw(sketch) 1623 if not res: 1624 return "" 1625 options = get_line_style_options(sketch) 1626 fill_options = get_fill_style_options(sketch) 1627 options += fill_options 1628 if sketch.smooth: 1629 options += ["smooth"] 1630 options = ", ".join(options) 1631 res += f"[{options}]" 1632 x, y = sketch.center[:2] 1633 width, height = sketch.width, sketch.height 1634 res += f"({x}, {y}) rectangle ({width}, {height});\n" 1635 end_scope = get_end_scope() 1636 1637 return begin_scope + res + end_scope 1638 1639def draw_ellipse_sketch(sketch): 1640 """Draws an ellipse sketch. 1641 1642 Args: 1643 sketch: The ellipse sketch object. 1644 1645 Returns: 1646 str: The TikZ code for the ellipse sketch. 1647 """ 1648 begin_scope = get_begin_scope() 1649 res = get_draw(sketch) 1650 if not res: 1651 return "" 1652 options = get_line_style_options(sketch) 1653 fill_options = get_fill_style_options(sketch) 1654 options += fill_options 1655 if sketch.smooth: 1656 options += ["smooth"] 1657 angle = degrees(sketch.angle) 1658 x, y = sketch.center[:2] 1659 if angle: 1660 options += [f"rotate around= {{{angle}:({x},{y})}}"] 1661 options = ", ".join(options) 1662 res += f"[{options}]" 1663 a = sketch.x_radius 1664 b = sketch.y_radius 1665 1666 res += f"({x}, {y}) ellipse ({a} and {b});\n" 1667 end_scope = get_end_scope() 1668 1669 return begin_scope + res + end_scope 1670 1671 1672def draw_arc_sketch(sketch): 1673 """Draws an arc sketch. 1674 1675 Args: 1676 sketch: The arc sketch object. 1677 1678 Returns: 1679 str: The TikZ code for the arc sketch. 1680 """ 1681 res = get_draw(sketch) 1682 if not res: 1683 return "" 1684 if sketch.closed: 1685 options = ["smooth cycle"] 1686 else: 1687 options = ['smooth'] 1688 1689 if sketch.back_style == BackStyle.PATTERN and sketch.fill and sketch.closed: 1690 options += get_pattern_options(sketch) 1691 if sketch.stroke: 1692 options += get_line_style_options(sketch) 1693 if sketch.closed and sketch.fill: 1694 options += get_fill_style_options(sketch) 1695 1696 if sketch.back_style == BackStyle.SHADING and sketch.fill and sketch.closed: 1697 options += get_shading_options(sketch) 1698 options = ", ".join(options) 1699 if options: 1700 res += f"[{options}] plot[tension=.8] coordinates" + "{" 1701 vertices = [round_point(v) for v in sketch.vertices] 1702 n = len(vertices) 1703 str_lines = [f"{vertices[0]}"] 1704 for i, vertice in enumerate(vertices[1:]): 1705 if (i + 1) % 8 == 0: 1706 if i == n - 1: 1707 str_lines.append(f" {vertice} \n") 1708 else: 1709 str_lines.append(f"\n\t {vertice} ") 1710 else: 1711 str_lines.append(f" {vertice} ") 1712 if sketch.closed: 1713 str_lines.append(" cycle;\n") 1714 else: 1715 str_lines.append("};\n") 1716 if res: 1717 res += "".join(str_lines) 1718 else: 1719 res = "".join(str_lines) 1720 return res 1721 1722def draw_bezier_sketch(sketch): 1723 """Draws a Bezier curve sketch. 1724 1725 Args: 1726 sketch: The Bezier curve sketch object. 1727 1728 Returns: 1729 str: The TikZ code for the Bezier curve sketch. 1730 """ 1731 begin_scope = get_begin_scope() 1732 res = get_draw(sketch) 1733 if not res: 1734 return "" 1735 options = get_line_style_options(sketch) 1736 options = ", ".join(options) 1737 res += f"[{options}]" 1738 p1, cp1, cp2, p2 = sketch.control_points 1739 x1, y1 = p1[:2] 1740 x2, y2 = cp1[:2] 1741 x3, y3 = cp2[:2] 1742 x4, y4 = p2[:2] 1743 res += f" ({x1}, {y1}) .. controls ({x2}, {y2}) and ({x3}, {y3}) .. ({x4}, {y4});\n" 1744 end_scope = get_end_scope() 1745 1746 return begin_scope + res + end_scope 1747 1748 1749def draw_line(line): 1750 """Tikz code for a line. 1751 1752 Args: 1753 line: The line object. 1754 1755 Returns: 1756 str: The TikZ code for the line. 1757 """ 1758 p1 = line.start[:2] 1759 p2 = line.end[:2] 1760 options = [] 1761 if line.line_width is not None: 1762 options.append(line.line_width) 1763 if line.color is not None: 1764 color = color2tikz(line.color) 1765 options.append(color) 1766 if line.dash_array is not None: 1767 options.append(line.dash_array) 1768 # options = [line.width, line.color, line.dash_array, line.cap, line.join] 1769 if line.line_width == 0: 1770 res = f"\\path[{', '.join(options)}] {p1} -- {p2};\n" 1771 else: 1772 res = f"\\draw[{', '.join(options)}] {p1} -- {p2};\n" 1773 1774 return res 1775 1776 1777def is_stroked(shape: Shape) -> bool: 1778 """Returns True if the shape is stroked. 1779 1780 Args: 1781 shape (Shape): The shape object. 1782 1783 Returns: 1784 bool: True if the shape is stroked, False otherwise. 1785 """ 1786 return shape.stroke and shape.line_color is not None and shape.line_width > 0 1787 1788 1789d_sketch_draw = { 1790 sg.Types.SHAPE: draw_shape_sketch, 1791 sg.Types.TAG_SKETCH: draw_tag_sketch, 1792 sg.Types.LACESKETCH: draw_lace_sketch, 1793 sg.Types.LINE: draw_line_sketch, 1794 sg.Types.CIRCLE: draw_circle_sketch, 1795 sg.Types.ELLIPSE: draw_shape_sketch, 1796 sg.Types.ARC: draw_arc_sketch, 1797 sg.Types.BATCH: draw_batch_sketch, 1798}
array(object, dtype=None, *, copy=True, order='K', subok=False, ndmin=0, like=None)
Create an array.
Parameters
object : array_like
An array, any object exposing the array interface, an object whose
__array__
method returns an array, or any (nested) sequence.
If object is a scalar, a 0-dimensional array containing object is
returned.
dtype : data-type, optional
The desired data-type for the array. If not given, NumPy will try to use
a default dtype
that can represent the values (by applying promotion
rules when necessary.)
copy : bool, optional
If True
(default), then the array data is copied. If None
,
a copy will only be made if __array__
returns a copy, if obj is
a nested sequence, or if a copy is needed to satisfy any of the other
requirements (dtype
, order
, etc.). Note that any copy of
the data is shallow, i.e., for arrays with object dtype, the new
array will point to the same objects. See Examples for ndarray.copy
.
For False
it raises a ValueError
if a copy cannot be avoided.
Default: True
.
order : {'K', 'A', 'C', 'F'}, optional
Specify the memory layout of the array. If object is not an array, the
newly created array will be in C order (row major) unless 'F' is
specified, in which case it will be in Fortran order (column major).
If object is an array the following holds.
===== ========= ===================================================
order no copy copy=True
===== ========= ===================================================
'K' unchanged F & C order preserved, otherwise most similar order
'A' unchanged F order if input is F and not C, otherwise C order
'C' C order C order
'F' F order F order
===== ========= ===================================================
When ``copy=None`` and a copy is made for other reasons, the result is
the same as if ``copy=True``, with some exceptions for 'A', see the
Notes section. The default order is 'K'.
subok : bool, optional
If True, then sub-classes will be passed-through, otherwise
the returned array will be forced to be a base-class array (default).
ndmin : int, optional
Specifies the minimum number of dimensions that the resulting
array should have. Ones will be prepended to the shape as
needed to meet this requirement.
like : array_like, optional
Reference object to allow the creation of arrays which are not
NumPy arrays. If an array-like passed in as like
supports
the __array_function__
protocol, the result will be defined
by it. In this case, it ensures the creation of an array object
compatible with that passed in via this argument.
*New in version 1.20.0.*
Returns
out : ndarray An array object satisfying the specified requirements.
See Also
empty_like : Return an empty array with shape and type of input. ones_like : Return an array of ones with shape and type of input. zeros_like : Return an array of zeros with shape and type of input. full_like : Return a new array with shape of input filled with value. empty : Return a new uninitialized array. ones : Return a new array setting values to one. zeros : Return a new array setting values to zero. full : Return a new array of given shape filled with value. copy: Return an array copy of the given object.
Notes
When order is 'A' and object
is an array in neither 'C' nor 'F' order,
and a copy is forced by a change in dtype, then the order of the result is
not necessarily 'C' as expected. This is likely a bug.
Examples
>>> np.array([1, 2, 3])
array([1, 2, 3])
Upcasting:
>>> np.array([1, 2, 3.0])
array([ 1., 2., 3.])
More than one dimension:
>>> np.array([[1, 2], [3, 4]])
array([[1, 2],
[3, 4]])
Minimum dimensions 2:
>>> np.array([1, 2, 3], ndmin=2)
array([[1, 2, 3]])
Type provided:
>>> np.array([1, 2, 3], dtype=complex)
array([ 1.+0.j, 2.+0.j, 3.+0.j])
Data-type consisting of more than one element:
>>> x = np.array([(1,2),(3,4)],dtype=[('a','<i4'),('b','<i4')])
>>> x['a']
array([1, 3])
Creating an array from sub-classes:
>>> np.array(np.asmatrix('1 2; 3 4'))
array([[1, 2],
[3, 4]])
>>> np.array(np.asmatrix('1 2; 3 4'), subok=True)
matrix([[1, 2],
[3, 4]])
52def scope_code_required(item: Union["Canvas", "Batch"]) -> bool: 53 """Check if a TikZ namespace is required for the item. 54 55 Args: 56 item (Union["Canvas", "Batch"]): The item to check. 57 58 Returns: 59 bool: True if a TikZ namespace is required, False otherwise. 60 """ 61 return ( 62 item.blend_mode is not None 63 or item.transparency_group 64 or (item.clip and item.mask) 65 )
Check if a TikZ namespace is required for the item.
Arguments:
- item (Union["Canvas", "Batch"]): The item to check.
Returns:
bool: True if a TikZ namespace is required, False otherwise.
68@dataclass 69class Tex: 70 """Tex class for generating tex code. 71 72 Attributes: 73 begin_document (str): The beginning of the document. 74 end_document (str): The end of the document. 75 begin_tikz (str): The beginning of the TikZ environment. 76 end_tikz (str): The end of the TikZ environment. 77 packages (List[str]): List of required TeX packages. 78 tikz_libraries (List[str]): List of required TikZ libraries. 79 tikz_code (str): The generated TikZ code. 80 sketches (List["Sketch"]): List of Sketch objects. 81 """ 82 83 begin_document: str = defaults["begin_doc"] 84 end_document: str = defaults["end_doc"] 85 begin_tikz: str = defaults["begin_tikz"] 86 end_tikz: str = defaults["end_tikz"] 87 packages: List[str] = None 88 tikz_libraries: List[str] = None 89 tikz_code: str = "" # Generated by the canvas by using sketches 90 sketches: List["Sketch"] = field(default_factory=list) # List of TexSketch objects 91 92 def __post_init__(self): 93 """Post-initialization method.""" 94 self.type = Types.TEX 95 common_properties(self) 96 97 def tex_code(self, canvas: "Canvas", aux_code: str) -> str: 98 """Generate the final TeX code. 99 100 Args: 101 canvas ("Canvas"): The canvas object. 102 aux_code (str): Auxiliary code to include. 103 104 Returns: 105 str: The final TeX code. 106 """ 107 doc_code = [] 108 for sketch in self.sketches: 109 if sketch.location == TexLoc.DOCUMENT: 110 doc_code.append(sketch.code) 111 doc_code = "\n".join(doc_code) 112 back_color = f"\\pagecolor{color2tikz(canvas.back_color)}" 113 self.begin_document = self.begin_document + back_color + "\n" 114 if canvas.limits is not None: 115 begin_tikz = self.begin_tikz + get_limits_code(canvas) + "\n" 116 else: 117 begin_tikz = self.begin_tikz + "\n" 118 if scope_code_required(canvas): 119 scope = get_canvas_scope(canvas) 120 code = ( 121 self.get_preamble(canvas) 122 + self.begin_document 123 + doc_code 124 + begin_tikz 125 + scope 126 + self.get_tikz_code() 127 + aux_code 128 + "\\end{scope}\n" 129 + self.end_tikz 130 + self.end_document 131 ) 132 else: 133 code = ( 134 self.get_preamble(canvas) 135 + self.begin_document 136 + doc_code 137 + begin_tikz 138 + self.get_tikz_code() 139 + aux_code 140 + self.end_tikz 141 + self.end_document 142 ) 143 144 return code 145 146 def get_doc_class(self, border: float, font_size: int) -> str: 147 """Returns the document class. 148 149 Args: 150 border (float): The border size. 151 font_size (int): The font size. 152 153 Returns: 154 str: The document class string. 155 """ 156 return f"\\documentclass[{font_size}pt,tikz,border={border}pt]{{standalone}}\n" 157 158 def get_tikz_code(self) -> str: 159 """Returns the TikZ code. 160 161 Returns: 162 str: The TikZ code. 163 """ 164 code = "" 165 for sketch in self.sketches: 166 if sketch.location == TexLoc.PICTURE: 167 code += sketch.text + "\n" 168 169 return code 170 171 def get_tikz_libraries(self) -> str: 172 """Returns the TikZ libraries. 173 174 Returns: 175 str: The TikZ libraries string. 176 """ 177 return f"\\usetikzlibrary{{{','.join(self.tikz_libraries)}}}\n" 178 179 def get_packages(self, canvas) -> str: 180 """Returns the required TeX packages. 181 182 Args: 183 canvas: The canvas object. 184 185 Returns: 186 str: The required TeX packages. 187 """ 188 tikz_libraries = [] 189 tikz_packages = ['tikz', 'pgf'] 190 for page in canvas.pages: 191 for sketch in page.sketches: 192 if hasattr(sketch, "draw_frame") and sketch.draw_frame: 193 if hasattr(sketch, "frame_shape") and sketch.frame_shape != FrameShape.RECTANGLE: 194 if "shapes.geometric" not in tikz_libraries: 195 tikz_libraries.append("shapes.geometric") 196 if sketch.draw_markers: 197 if 'patterns' not in tikz_libraries: 198 tikz_libraries.append("patterns") 199 tikz_libraries.append("patterns.meta") 200 tikz_libraries.append("backgrounds") 201 tikz_libraries.append("shadings") 202 if sketch.line_dash_array: 203 if 'patterns' not in tikz_libraries: 204 tikz_libraries.append("patterns") 205 if sketch.subtype == Types.TAG_SKETCH: 206 if "fontspec" not in tikz_packages: 207 tikz_packages.append("fontspec") 208 else: 209 if hasattr(sketch, "marker_type") and sketch.marker_type == "indices": 210 if "fontspec" not in tikz_packages: 211 tikz_packages.append("fontspec") 212 if hasattr(sketch, 'back_style'): 213 if sketch.back_style == BackStyle.COLOR: 214 if "xcolor" not in tikz_packages: 215 tikz_packages.append("xcolor") 216 if sketch.back_style == BackStyle.SHADING: 217 if "shadings" not in tikz_libraries: 218 tikz_libraries.append("shadings") 219 if sketch.back_style == BackStyle.PATTERN: 220 if "patterns" not in tikz_libraries: 221 tikz_libraries.append("patterns") 222 tikz_libraries.append("patterns.meta") 223 224 225 return tikz_libraries, tikz_packages 226 227 def get_preamble(self, canvas) -> str: 228 """Returns the TeX preamble. 229 230 Args: 231 canvas: The canvas object. 232 233 Returns: 234 str: The TeX preamble. 235 """ 236 libraries, packages = self.get_packages(canvas) 237 238 if packages: 239 packages = f'\\usepackage{{{",".join(packages)}}}\n' 240 if 'fontspec' in packages: 241 fonts_section = f"""\\setmainfont{{{defaults['main_font']}}} 242\\setsansfont{{{defaults['sans_font']}}} 243\\setmonofont{{{defaults['mono_font']}}}\n""" 244 245 if libraries: 246 libraries = f'\\usetikzlibrary{{{",".join(libraries)}}}\n' 247 248 if canvas.border is None: 249 border = defaults["border"] 250 elif isinstance(canvas.border, (int, float)): 251 border = canvas.border 252 else: 253 raise ValueError("Canvas.border must be a positive numeric value.") 254 if border < 0: 255 raise ValueError("Canvas.border must be a positive numeric value.") 256 doc_class = self.get_doc_class(border, defaults["font_size"]) 257 # Check if different fonts are used 258 fonts_section = "" 259 fonts = canvas.get_fonts_list() 260 for font in fonts: 261 if font is None: 262 continue 263 font_family = font.replace(" ", "") 264 fonts_section += f"\\newfontfamily\\{font_family}[Scale=1.0]{{{font}}}\n" 265 preamble = f"{doc_class}{packages}{libraries}{fonts_section}" 266 267 indices = False 268 for sketch in canvas.active_page.sketches: 269 if hasattr(sketch, "marker_type") and sketch.marker_type == "indices": 270 indices = True 271 break 272 if indices: 273 font_family = defaults["indices_font_family"] 274 font_size = defaults["indices_font_size"] 275 count = 0 276 for sketch in canvas.active_page.sketches: 277 if hasattr(sketch, "marker_type") and sketch.marker_type == "indices": 278 preamble += "\\tikzset{\n" 279 node_style = ( 280 f"nodestyle{count}/.style={{draw, circle, gray, " 281 f"text=black, fill=white, line width = .5, inner sep=.5, " 282 f"font=\\{font_family}\\{font_size}}}\n}}\n" 283 ) 284 preamble += node_style 285 count += 1 286 for sketch in canvas.active_page.sketches: 287 if sketch.subtype == Types.TEX_SKETCH: 288 if sketch.location == TexLoc.PREAMBLE: 289 preamble += sketch.code + "\n" 290 return preamble
Tex class for generating tex code.
Attributes:
- begin_document (str): The beginning of the document.
- end_document (str): The end of the document.
- begin_tikz (str): The beginning of the TikZ environment.
- end_tikz (str): The end of the TikZ environment.
- packages (List[str]): List of required TeX packages.
- tikz_libraries (List[str]): List of required TikZ libraries.
- tikz_code (str): The generated TikZ code.
- sketches (List["Sketch"]): List of Sketch objects.
97 def tex_code(self, canvas: "Canvas", aux_code: str) -> str: 98 """Generate the final TeX code. 99 100 Args: 101 canvas ("Canvas"): The canvas object. 102 aux_code (str): Auxiliary code to include. 103 104 Returns: 105 str: The final TeX code. 106 """ 107 doc_code = [] 108 for sketch in self.sketches: 109 if sketch.location == TexLoc.DOCUMENT: 110 doc_code.append(sketch.code) 111 doc_code = "\n".join(doc_code) 112 back_color = f"\\pagecolor{color2tikz(canvas.back_color)}" 113 self.begin_document = self.begin_document + back_color + "\n" 114 if canvas.limits is not None: 115 begin_tikz = self.begin_tikz + get_limits_code(canvas) + "\n" 116 else: 117 begin_tikz = self.begin_tikz + "\n" 118 if scope_code_required(canvas): 119 scope = get_canvas_scope(canvas) 120 code = ( 121 self.get_preamble(canvas) 122 + self.begin_document 123 + doc_code 124 + begin_tikz 125 + scope 126 + self.get_tikz_code() 127 + aux_code 128 + "\\end{scope}\n" 129 + self.end_tikz 130 + self.end_document 131 ) 132 else: 133 code = ( 134 self.get_preamble(canvas) 135 + self.begin_document 136 + doc_code 137 + begin_tikz 138 + self.get_tikz_code() 139 + aux_code 140 + self.end_tikz 141 + self.end_document 142 ) 143 144 return code
Generate the final TeX code.
Arguments:
- canvas ("Canvas"): The canvas object.
- aux_code (str): Auxiliary code to include.
Returns:
str: The final TeX code.
146 def get_doc_class(self, border: float, font_size: int) -> str: 147 """Returns the document class. 148 149 Args: 150 border (float): The border size. 151 font_size (int): The font size. 152 153 Returns: 154 str: The document class string. 155 """ 156 return f"\\documentclass[{font_size}pt,tikz,border={border}pt]{{standalone}}\n"
Returns the document class.
Arguments:
- border (float): The border size.
- font_size (int): The font size.
Returns:
str: The document class string.
158 def get_tikz_code(self) -> str: 159 """Returns the TikZ code. 160 161 Returns: 162 str: The TikZ code. 163 """ 164 code = "" 165 for sketch in self.sketches: 166 if sketch.location == TexLoc.PICTURE: 167 code += sketch.text + "\n" 168 169 return code
Returns the TikZ code.
Returns:
str: The TikZ code.
171 def get_tikz_libraries(self) -> str: 172 """Returns the TikZ libraries. 173 174 Returns: 175 str: The TikZ libraries string. 176 """ 177 return f"\\usetikzlibrary{{{','.join(self.tikz_libraries)}}}\n"
Returns the TikZ libraries.
Returns:
str: The TikZ libraries string.
179 def get_packages(self, canvas) -> str: 180 """Returns the required TeX packages. 181 182 Args: 183 canvas: The canvas object. 184 185 Returns: 186 str: The required TeX packages. 187 """ 188 tikz_libraries = [] 189 tikz_packages = ['tikz', 'pgf'] 190 for page in canvas.pages: 191 for sketch in page.sketches: 192 if hasattr(sketch, "draw_frame") and sketch.draw_frame: 193 if hasattr(sketch, "frame_shape") and sketch.frame_shape != FrameShape.RECTANGLE: 194 if "shapes.geometric" not in tikz_libraries: 195 tikz_libraries.append("shapes.geometric") 196 if sketch.draw_markers: 197 if 'patterns' not in tikz_libraries: 198 tikz_libraries.append("patterns") 199 tikz_libraries.append("patterns.meta") 200 tikz_libraries.append("backgrounds") 201 tikz_libraries.append("shadings") 202 if sketch.line_dash_array: 203 if 'patterns' not in tikz_libraries: 204 tikz_libraries.append("patterns") 205 if sketch.subtype == Types.TAG_SKETCH: 206 if "fontspec" not in tikz_packages: 207 tikz_packages.append("fontspec") 208 else: 209 if hasattr(sketch, "marker_type") and sketch.marker_type == "indices": 210 if "fontspec" not in tikz_packages: 211 tikz_packages.append("fontspec") 212 if hasattr(sketch, 'back_style'): 213 if sketch.back_style == BackStyle.COLOR: 214 if "xcolor" not in tikz_packages: 215 tikz_packages.append("xcolor") 216 if sketch.back_style == BackStyle.SHADING: 217 if "shadings" not in tikz_libraries: 218 tikz_libraries.append("shadings") 219 if sketch.back_style == BackStyle.PATTERN: 220 if "patterns" not in tikz_libraries: 221 tikz_libraries.append("patterns") 222 tikz_libraries.append("patterns.meta") 223 224 225 return tikz_libraries, tikz_packages
Returns the required TeX packages.
Arguments:
- canvas: The canvas object.
Returns:
str: The required TeX packages.
227 def get_preamble(self, canvas) -> str: 228 """Returns the TeX preamble. 229 230 Args: 231 canvas: The canvas object. 232 233 Returns: 234 str: The TeX preamble. 235 """ 236 libraries, packages = self.get_packages(canvas) 237 238 if packages: 239 packages = f'\\usepackage{{{",".join(packages)}}}\n' 240 if 'fontspec' in packages: 241 fonts_section = f"""\\setmainfont{{{defaults['main_font']}}} 242\\setsansfont{{{defaults['sans_font']}}} 243\\setmonofont{{{defaults['mono_font']}}}\n""" 244 245 if libraries: 246 libraries = f'\\usetikzlibrary{{{",".join(libraries)}}}\n' 247 248 if canvas.border is None: 249 border = defaults["border"] 250 elif isinstance(canvas.border, (int, float)): 251 border = canvas.border 252 else: 253 raise ValueError("Canvas.border must be a positive numeric value.") 254 if border < 0: 255 raise ValueError("Canvas.border must be a positive numeric value.") 256 doc_class = self.get_doc_class(border, defaults["font_size"]) 257 # Check if different fonts are used 258 fonts_section = "" 259 fonts = canvas.get_fonts_list() 260 for font in fonts: 261 if font is None: 262 continue 263 font_family = font.replace(" ", "") 264 fonts_section += f"\\newfontfamily\\{font_family}[Scale=1.0]{{{font}}}\n" 265 preamble = f"{doc_class}{packages}{libraries}{fonts_section}" 266 267 indices = False 268 for sketch in canvas.active_page.sketches: 269 if hasattr(sketch, "marker_type") and sketch.marker_type == "indices": 270 indices = True 271 break 272 if indices: 273 font_family = defaults["indices_font_family"] 274 font_size = defaults["indices_font_size"] 275 count = 0 276 for sketch in canvas.active_page.sketches: 277 if hasattr(sketch, "marker_type") and sketch.marker_type == "indices": 278 preamble += "\\tikzset{\n" 279 node_style = ( 280 f"nodestyle{count}/.style={{draw, circle, gray, " 281 f"text=black, fill=white, line width = .5, inner sep=.5, " 282 f"font=\\{font_family}\\{font_size}}}\n}}\n" 283 ) 284 preamble += node_style 285 count += 1 286 for sketch in canvas.active_page.sketches: 287 if sketch.subtype == Types.TEX_SKETCH: 288 if sketch.location == TexLoc.PREAMBLE: 289 preamble += sketch.code + "\n" 290 return preamble
Returns the TeX preamble.
Arguments:
- canvas: The canvas object.
Returns:
str: The TeX preamble.
293def get_back_grid_code(grid: Grid, canvas: "Canvas") -> str: 294 """Page background grid code. 295 296 Args: 297 grid (Grid): The grid object. 298 canvas ("Canvas"): The canvas object. 299 300 Returns: 301 str: The background grid code. 302 """ 303 # \usetikzlibrary{backgrounds} 304 # \begin{scope}[on background layer] 305 # \fill[gray] (current bounding box.south west) rectangle 306 # (current bounding box.north east); 307 # \draw[white,step=.5cm] (current bounding box.south west) grid 308 # (current bounding box.north east); 309 # \end{scope} 310 grid = canvas.active_page.grid 311 back_color = color2tikz(grid.back_color) 312 line_color = color2tikz(grid.line_color) 313 step = grid.spacing 314 lines = ["\\begin{scope}[on background layer]\n"] 315 lines.append(f"\\fill[color={back_color}] (current bounding box.south west) ") 316 lines.append("rectangle (current bounding box.north east);\n") 317 options = [] 318 if grid.line_dash_array is not None: 319 options.append(f"dashed, dash pattern={get_dash_pattern(grid.line_dash_array)}") 320 if grid.line_width is not None: 321 options.append(f"line width={grid.line_width}") 322 if options: 323 options = ",".join(options) 324 lines.append(f"\\draw[color={line_color}, step={step}, {options}]") 325 else: 326 lines.append(f"\\draw[color={line_color},step={step}]") 327 lines.append("(current bounding box.south west)") 328 lines.append(" grid (current bounding box.north east);\n") 329 lines.append("\\end{scope}\n") 330 331 return "".join(lines)
Page background grid code.
Arguments:
- grid (Grid): The grid object.
- canvas ("Canvas"): The canvas object.
Returns:
str: The background grid code.
334def get_limits_code(canvas: "Canvas") -> str: 335 """Get the limits of the canvas for clipping. 336 337 Args: 338 canvas ("Canvas"): The canvas object. 339 340 Returns: 341 str: The limits code for clipping. 342 """ 343 xmin, ymin, xmax, ymax = canvas.limits 344 points = [(xmin, ymin), (xmin, ymax), (xmax, ymax), (xmax, ymin)] 345 vertices = homogenize(points) @ canvas.xform_matrix 346 coords = " ".join([f"({v[0]}, {v[1]})" for v in vertices]) 347 348 return f"\\clip plot[] coordinates {{{coords}}};\n"
Get the limits of the canvas for clipping.
Arguments:
- canvas ("Canvas"): The canvas object.
Returns:
str: The limits code for clipping.
351def get_back_code(canvas: "Canvas") -> str: 352 """Get the background code for the canvas. 353 354 Args: 355 canvas ("Canvas"): The canvas object. 356 357 Returns: 358 str: The background code. 359 """ 360 back_color = color2tikz(canvas.back_color) 361 return f"\\pagecolor{back_color}\n"
Get the background code for the canvas.
Arguments:
- canvas ("Canvas"): The canvas object.
Returns:
str: The background code.
364def get_tex_code(canvas: "Canvas") -> str: 365 """Convert the sketches in the Canvas to TikZ code. 366 367 Args: 368 canvas ("Canvas"): The canvas object. 369 370 Returns: 371 str: The TikZ code. 372 """ 373 374 def get_sketch_code(sketch, canvas, ind): 375 """Get the TikZ code for a sketch. 376 377 Args: 378 sketch: The sketch object. 379 canvas: The canvas object. 380 ind: The index. 381 382 Returns: 383 tuple: The TikZ code and the updated index. 384 """ 385 if sketch.subtype == Types.TAG_SKETCH: 386 code = draw_tag_sketch(sketch) 387 elif sketch.subtype == Types.BBOX_SKETCH: 388 code = draw_bbox_sketch(sketch) 389 elif sketch.subtype == Types.PATTERN_SKETCH: 390 code = draw_pattern_sketch(sketch) 391 elif sketch.subtype == Types.TEX_SKETCH: 392 if sketch.location == TexLoc.NONE: 393 code = sketch.code 394 else: 395 if sketch.draw_markers and sketch.marker_type == MarkerType.INDICES: 396 code = draw_shape_sketch(sketch, ind) 397 ind += 1 398 else: 399 code = draw_shape_sketch(sketch) 400 401 return code, ind 402 403 pages = canvas.pages 404 405 if pages: 406 for i, page in enumerate(pages): 407 sketches = page.sketches 408 back_color = f"\\pagecolor{color2tikz(page.back_color)}" 409 if i == 0: 410 code = [back_color] 411 else: 412 code.append(defaults["end_tikz"]) 413 code.append("\\newpage") 414 code.append(defaults["begin_tikz"]) 415 ind = 0 416 for sketch in sketches: 417 sketch_code, ind = get_sketch_code(sketch, canvas, ind) 418 code.append(sketch_code) 419 420 code = "\n".join(code) 421 else: 422 raise ValueError("No pages found in the canvas.") 423 return canvas.tex.tex_code(canvas, code)
Convert the sketches in the Canvas to TikZ code.
Arguments:
- canvas ("Canvas"): The canvas object.
Returns:
str: The TikZ code.
426class Grid(sg.Shape): 427 """Grid shape. 428 429 Args: 430 p1: (x_min, y_min) 431 p2: (x_max, y_max) 432 dx: x step 433 dy: y step 434 """ 435 436 def __init__(self, p1, p2, dx, dy, **kwargs): 437 """ 438 Args: 439 p1: (x_min, y_min) 440 p2: (x_max, y_max) 441 dx: x step 442 dy: y step 443 """ 444 self.p1 = p1 445 self.p2 = p2 446 self.dx = dx 447 self.dy = dy 448 self.primary_points = sg.Points([p1, p2]) 449 self.closed = False 450 self.fill = False 451 self.stroke = True 452 self._b_box = None 453 super().__init__([p1, p2], xform_matrix=None, subtype=sg.Types.GRID, **kwargs)
Grid shape.
Arguments:
- p1: (x_min, y_min)
- p2: (x_max, y_max)
- dx: x step
- dy: y step
436 def __init__(self, p1, p2, dx, dy, **kwargs): 437 """ 438 Args: 439 p1: (x_min, y_min) 440 p2: (x_max, y_max) 441 dx: x step 442 dy: y step 443 """ 444 self.p1 = p1 445 self.p2 = p2 446 self.dx = dx 447 self.dy = dy 448 self.primary_points = sg.Points([p1, p2]) 449 self.closed = False 450 self.fill = False 451 self.stroke = True 452 self._b_box = None 453 super().__init__([p1, p2], xform_matrix=None, subtype=sg.Types.GRID, **kwargs)
Arguments:
- p1: (x_min, y_min)
- p2: (x_max, y_max)
- dx: x step
- dy: y step
Inherited Members
456def get_min_size(sketch: ShapeSketch) -> str: 457 """Returns the minimum size of the tag node. 458 459 Args: 460 sketch (ShapeSketch): The shape sketch object. 461 462 Returns: 463 str: The minimum size of the tag node. 464 """ 465 options = [] 466 if sketch.frame_shape == "rectangle": 467 if sketch.frame_min_width is None: 468 width = defaults["min_width"] 469 else: 470 width = sketch.frame_min_width 471 if sketch.frame_min_height is None: 472 height = defaults["min_height"] 473 else: 474 height = sketch.frame_min_height 475 options.append(f"minimum width = {width}") 476 options.append(f"minimum height = {height}") 477 else: 478 if sketch.frame_min_size is None: 479 min_size = defaults["min_size"] 480 else: 481 min_size = sketch.frame_min_size 482 options.append(f"minimum size = {min_size}") 483 484 return options
Returns the minimum size of the tag node.
Arguments:
- sketch (ShapeSketch): The shape sketch object.
Returns:
str: The minimum size of the tag node.
487def frame_options(sketch: TagSketch) -> List[str]: 488 """Returns the options for the frame of the tag node. 489 490 Args: 491 sketch (TagSketch): The tag sketch object. 492 493 Returns: 494 List[str]: The options for the frame of the tag node. 495 """ 496 options = [] 497 if sketch.draw_frame: 498 options.append(sketch.frame_shape) 499 line_options = get_line_style_options(sketch, frame=True) 500 if line_options: 501 options.extend(line_options) 502 fill_options = get_fill_style_options(sketch, frame=True) 503 if fill_options: 504 options.extend(fill_options) 505 if sketch.text in [None, ""]: 506 min_size = get_min_size(sketch) 507 if min_size: 508 options.extend(min_size) 509 510 return options
Returns the options for the frame of the tag node.
Arguments:
- sketch (TagSketch): The tag sketch object.
Returns:
List[str]: The options for the frame of the tag node.
513def color2tikz(color): 514 """Converts a Color object to a TikZ color string. 515 516 Args: 517 color (Color): The color object. 518 519 Returns: 520 str: The TikZ color string. 521 """ 522 # \usepackage{xcolor} 523 # \tikz\node[rounded corners, fill={rgb,255:red,21; green,66; blue,128}, 524 # text=white, draw=black] {hello world}; 525 # \definecolor{mycolor}{rgb}{1,0.2,0.3} 526 # \definecolor{mycolor}{R_g_b}{255,51,76} 527 # \definecolor{mypink1}{rgb}{0.858, 0.188, 0.478} 528 # \definecolor{mypink2}{R_g_b}{219, 48, 122} 529 # \definecolor{mypink3}{cmyk}{0, 0.7808, 0.4429, 0.1412} 530 # \definecolor{mygray}{gray}{0.6} 531 if color is None: 532 r, g, b, _ = 255, 255, 255, 255 533 return f"{{rgb,255:red,{r}; green,{g}; blue,{b}}}" 534 r, g, b = color.rgb255 535 536 return f"{{rgb,255:red,{r}; green,{g}; blue,{b}}}"
Converts a Color object to a TikZ color string.
Arguments:
- color (Color): The color object.
Returns:
str: The TikZ color string.
539def get_scope_options(item: Union["Canvas", "Sketch"]) -> str: 540 """Used for creating namespaces in TikZ. 541 542 Args: 543 item (Union["Canvas", "Sketch"]): The item to get scope options for. 544 545 Returns: 546 str: The scope options as a string. 547 """ 548 options = [] 549 550 if item.blend_group: 551 options.append(f"blend group={item.blend_mode}") 552 elif item.blend_mode: 553 options.append(f"blend mode={item.blend_mode}") 554 if item.fill_alpha not in [None, 1]: 555 options.append(f"fill opacity={item.fill_alpha}") 556 if item.line_alpha not in [None, 1]: 557 options.append(f"draw opacity={item.line_alpha}") 558 if item.text_alpha not in [None, 1]: 559 options.append(f"text opacity={item.alpha}") 560 if item.alpha not in [None, 1]: 561 options.append(f"opacity={item.alpha}") 562 if item.even_odd_rule: 563 options.append("even odd rule") 564 if item.transparency_group: 565 options.append("transparency group") 566 567 return ",".join(options)
Used for creating namespaces in TikZ.
Arguments:
- item (Union["Canvas", "Sketch"]): The item to get scope options for.
Returns:
str: The scope options as a string.
570def get_clip_code(item: Union["Sketch", "Canvas"]) -> str: 571 """Returns the clip code for a sketch or Canvas. 572 573 Args: 574 item (Union["Sketch", "Canvas"]): The item to get clip code for. 575 576 Returns: 577 str: The clip code as a string. 578 """ 579 if item.mask.subtype == Types.CIRCLE: 580 x, y = item.mask.center[:2] 581 res = f"\\clip({x}, {y}) circle ({item.mask.radius});\n" 582 elif item.mask.subtype == Types.RECTANGLE: 583 x, y = item.mask.center[:2] 584 width, height = item.mask.width, item.mask.height 585 res = f"\\clip({x}, {y}) rectangle ({width}, {height});\n" 586 elif item.mask.subtype == Types.SHAPE: 587 # vertices = item.mask.primary_points.homogen_coords 588 # coords = " ".join([f"({v[0]}, {v[1]})" for v in vertices]) 589 # res = f"\\clip plot[] coordinates {{{coords}}};\n" 590 x, y = item.mask.origin[:2] 591 width, height = item.mask.width, item.mask.height 592 res = f"\\clip({x}, {y}) rectangle ({width}, {height});\n" 593 else: 594 res = "" 595 596 return res
Returns the clip code for a sketch or Canvas.
Arguments:
- item (Union["Sketch", "Canvas"]): The item to get clip code for.
Returns:
str: The clip code as a string.
599def get_canvas_scope(canvas): 600 """Returns the TikZ code for the canvas scope. 601 602 Args: 603 canvas: The canvas object. 604 605 Returns: 606 str: The TikZ code for the canvas scope. 607 """ 608 options = get_scope_options(canvas) 609 res = f"\\begin{{scope}}[{options}]\n" 610 if (canvas.clip and canvas.mask) or canvas.size is not None: 611 res += get_clip_code(canvas) 612 613 return res
Returns the TikZ code for the canvas scope.
Arguments:
- canvas: The canvas object.
Returns:
str: The TikZ code for the canvas scope.
616def draw_batch_sketch(sketch, canvas): 617 """Converts a BatchSketch to TikZ code. 618 619 Args: 620 sketch: The BatchSketch object. 621 canvas: The canvas object. 622 623 Returns: 624 str: The TikZ code for the BatchSketch. 625 """ 626 options = get_scope_options(sketch) 627 if options: 628 res = f"\\begin{{scope}}[{options}]\n" 629 else: 630 res = "" 631 if sketch.clip and sketch.mask: 632 res += get_clip_code(sketch) 633 for item in sketch.items: 634 if item.subtype in d_sketch_draw: 635 res += d_sketch_draw[item.subtype](item, canvas) 636 else: 637 raise ValueError(f"Sketch type {item.subtype} not supported.") 638 639 if sketch.clip and sketch.mask: 640 res += get_clip_code(sketch) 641 if options: 642 res += "\\end{scope}\n" 643 644 return res
Converts a BatchSketch to TikZ code.
Arguments:
- sketch: The BatchSketch object.
- canvas: The canvas object.
Returns:
str: The TikZ code for the BatchSketch.
646def draw_bbox_sketch(sketch): 647 """Converts a BBoxSketch to TikZ code. 648 649 Args: 650 sketch: The BBoxSketch object. 651 canvas: The canvas object. 652 653 Returns: 654 str: The TikZ code for the BBoxSketch. 655 """ 656 attrib_map = { 657 "line_color": "color", 658 "line_width": "line width", 659 "line_dash_array": "dash pattern", 660 } 661 attrib_list = ["line_color", "line_width", "line_dash_array"] 662 options = sg_to_tikz(sketch, attrib_list, attrib_map) 663 options = ", ".join(options) 664 res = f"\\draw[{options}]" 665 x1, y1 = sketch.vertices[1] 666 x2, y2 = sketch.vertices[3] 667 res += f"({x1}, {y1}) rectangle ({x2}, {y2});\n" 668 669 return res
Converts a BBoxSketch to TikZ code.
Arguments:
- sketch: The BBoxSketch object.
- canvas: The canvas object.
Returns:
str: The TikZ code for the BBoxSketch.
671def draw_lace_sketch(item): 672 """Converts a LaceSketch to TikZ code. 673 674 Args: 675 item: The LaceSketch object. 676 677 Returns: 678 str: The TikZ code for the LaceSketch. 679 """ 680 if item.draw_fragments: 681 for fragment in item.fragments: 682 draw_shape_sketch(fragment) 683 if item.draw_plaits: 684 for plait in item.plaits: 685 plait.fill = True 686 draw_shape_sketch(plait)
Converts a LaceSketch to TikZ code.
Arguments:
- item: The LaceSketch object.
Returns:
str: The TikZ code for the LaceSketch.
689def get_draw(sketch): 690 """Returns the draw command for sketches. 691 692 Args: 693 sketch: The sketch object. 694 695 Returns: 696 str: The draw command as a string. 697 """ 698 # sketch.closed, sketch.fill, sketch.stroke, shading 699 decision_table = { 700 (True, True, True, True): "\\shadedraw", 701 (True, True, True, False): "\\filldraw", 702 (True, True, False, True): "\\shade", 703 (True, True, False, False): "\\fill", 704 (True, False, True, True): "\\draw", 705 (True, False, True, False): "\\draw", 706 (True, False, False, True): False, 707 (True, False, False, False): False, 708 (False, True, True, True): "\\draw", 709 (False, True, True, False): "\\draw", 710 (False, True, False, True): False, 711 (False, True, False, False): False, 712 (False, False, True, True): "\\draw", 713 (False, False, True, False): "\\draw", 714 (False, False, False, True): False, 715 (False, False, False, False): False, 716 } 717 if sketch.markers_only: 718 res = "\\draw" 719 else: 720 if hasattr(sketch, 'back_style'): 721 shading = sketch.back_style == BackStyle.SHADING 722 else: 723 shading = False 724 if not hasattr(sketch, 'closed'): 725 closed = False 726 else: 727 closed = sketch.closed 728 if not hasattr(sketch, 'fill'): 729 fill = False 730 else: 731 fill = sketch.fill 732 if not hasattr(sketch, 'stroke'): 733 stroke = False 734 else: 735 stroke = sketch.stroke 736 737 res = decision_table[(closed, fill, stroke, shading)] 738 739 return res
Returns the draw command for sketches.
Arguments:
- sketch: The sketch object.
Returns:
str: The draw command as a string.
742def get_frame_options(sketch): 743 """Returns the options for the frame of a TagSketch. 744 745 Args: 746 sketch: The TagSketch object. 747 748 Returns: 749 list: The options for the frame of the TagSketch. 750 """ 751 options = get_line_style_options(sketch) 752 options += get_fill_style_options(sketch) 753 if sketch.text in [None, ""]: 754 if sketch.frame.frame_shape == "rectangle": 755 width = sketch.frame.min_width 756 height = sketch.frame.min_height 757 if not width: 758 width = defaults["min_width"] 759 if not height: 760 height = defaults["min_height"] 761 options += "minimum width = {width}, minimum height = {height}" 762 else: 763 size = sketch.frame.min_size 764 if not size: 765 size = defaults["min_size"] 766 options += f"minimum size = {size}" 767 return options
Returns the options for the frame of a TagSketch.
Arguments:
- sketch: The TagSketch object.
Returns:
list: The options for the frame of the TagSketch.
770def draw_tag_sketch(sketch): 771 """Converts a TagSketch to TikZ code. 772 773 Args: 774 sketch: The TagSketch object. 775 canvas: The canvas object. 776 777 Returns: 778 str: The TikZ code for the TagSketch. 779 """ 780 # \node at (0,0) {some text}; 781 def get_font_family(sketch): 782 default_fonts = [defaults['main_font'], defaults['sans_font'], defaults['mono_font']] 783 784 if sketch.font_family in default_fonts: 785 if sketch.font_family == defaults['main_font']: 786 res = 'tex_family', '' 787 elif sketch.font_family == defaults['sans_font']: 788 res = 'tex_family', 'textsf' 789 else: # defaults['mono_font'] 790 res = 'tex_family', 'texttt' 791 elif sketch.font_family: 792 if isinstance(sketch.font_family, FontFamily): 793 if sketch.font_family == FontFamily.SANSSERIF: 794 res = 'tex_family', 'textsf' 795 elif sketch.font_family == FontFamily.MONOSPACE: 796 res = 'tex_family', 'texttt' 797 else: 798 res = 'tex_family', 'textrm' 799 800 elif isinstance(sketch.font_family, str): 801 res = 'new_family', sketch.font_family.replace(" ", "") 802 803 else: 804 raise ValueError(f"Font family {sketch.font_family} not supported.") 805 else: 806 res = 'no_family', None 807 808 return res 809 810 def get_font_size(sketch): 811 812 if sketch.font_size: 813 if isinstance(sketch.font_size, FontSize): 814 res = 'tex_size', sketch.font_size.value 815 else: 816 res = 'num_size', sketch.font_size 817 else: 818 res = 'no_size', None 819 820 return res 821 822 res = [] 823 x, y = sketch.pos[:2] 824 825 options = "" 826 if sketch.draw_frame: 827 options += "draw" 828 if sketch.stroke: 829 if sketch.frame_shape != FrameShape.RECTANGLE: 830 options += f", {sketch.frame_shape}, " 831 line_style_options = get_line_style_options(sketch) 832 if line_style_options: 833 options += ', ' + ', '.join(line_style_options) 834 if sketch.frame_inner_sep: 835 options += f", inner sep={sketch.frame_inner_sep}" 836 if sketch.minimum_width: 837 options += f", minimum width={sketch.minimum_width}" 838 if sketch.smooth and sketch.frame_shape not in [ 839 FrameShape.CIRCLE, 840 FrameShape.ELLIPSE, 841 ]: 842 options += ", smooth" 843 844 if sketch.fill and sketch.back_color: 845 options += f", fill={color2tikz(sketch.frame_back_color)}" 846 if sketch.anchor: 847 options += f", anchor={sketch.anchor.value}" 848 if sketch.back_style == BackStyle.SHADING and sketch.fill: 849 shading_options = get_shading_options(sketch)[0] 850 options += ", " + shading_options 851 if sketch.back_style == BackStyle.PATTERN and sketch.fill: 852 pattern_options = get_pattern_options(sketch)[0] 853 options += ", " + pattern_options 854 if sketch.align != defaults["tag_align"]: 855 options += f", align={sketch.align.value}" 856 if sketch.text_width: 857 options += f", text width={sketch.text_width}" 858 859 860# no_family, tex_family, new_family 861# no_size, tex_size, num_size 862 863# num_size and new_family {\fontsize{20}{24} \selectfont \Verdana ABCDEFG Hello, World! 25} 864# tex_size and new_family {\large{\selectfont \Verdana ABCDEFG Hello, World! 50}} 865# no_size and new_family {\selectfont \Verdana ABCDEFG Hello, World! 50} 866 867# tex_family {\textsc{\textit{\textbf{\Huge{\texttt{ABCDG Just a test -50}}}}}}; 868 869# no_family {\textsc{\textit{\textbf{\Huge{ABCDG Just a test -50}}}}}; 870 871 if sketch.font_color is not None and sketch.font_color != defaults["font_color"]: 872 options += f", text={color2tikz(sketch.font_color)}" 873 family, font_family = get_font_family(sketch) 874 size, font_size = get_font_size(sketch) 875 tex_text = '' 876 if sketch.small_caps: 877 tex_text += '\\textsc{' 878 879 if sketch.italic: 880 tex_text += '\\textit{' 881 882 if sketch.bold: 883 tex_text += '\\textbf{' 884 885 if size == 'num_size': 886 f_size = font_size 887 f_size2 = ceil(font_size * 1.2) 888 tex_text += f"\\fontsize{{{f_size}}}{{{f_size2}}}\\selectfont " 889 890 elif size == 'tex_size': 891 tex_text += f"\\{font_size}{{\\selectfont " 892 893 else: 894 tex_text += "\\selectfont " 895 896 if family == 'new_family': 897 tex_text += f"\\{font_family} {sketch.text}}}" 898 899 elif family == 'tex_family': 900 if font_family: 901 tex_text += f"\\{font_family}{{ {sketch.text}}}}}" 902 else: 903 tex_text += f"{{ {sketch.text}}}" 904 else: # no_family 905 tex_text += f"{{ {sketch.text}}}" 906 907 tex_text = '{' + tex_text 908 909 open_braces = tex_text.count('{') 910 close_braces = tex_text.count('}') 911 tex_text = tex_text + '}' * (open_braces - close_braces) 912 913 res.append(f"\\node[{options}] at ({x}, {y}) {tex_text};\n") 914 915 return ''.join(res)
Converts a TagSketch to TikZ code.
Arguments:
- sketch: The TagSketch object.
- canvas: The canvas object.
Returns:
str: The TikZ code for the TagSketch.
918def get_dash_pattern(line_dash_array): 919 """Returns the dash pattern for a line. 920 921 Args: 922 line_dash_array: The dash array for the line. 923 924 Returns: 925 str: The dash pattern as a string. 926 """ 927 dash_pattern = [] 928 for i, dash in enumerate(line_dash_array): 929 if i % 2 == 0: 930 dash_pattern.extend(["on", f"{dash}pt"]) 931 else: 932 dash_pattern.extend(["off", f"{dash}pt"]) 933 934 return " ".join(dash_pattern)
Returns the dash pattern for a line.
Arguments:
- line_dash_array: The dash array for the line.
Returns:
str: The dash pattern as a string.
937def sg_to_tikz(sketch, attrib_list, attrib_map, conditions=None, exceptions=None): 938 """Converts the attributes of a sketch to TikZ options. 939 940 Args: 941 sketch: The sketch object. 942 attrib_list: The list of attributes to convert. 943 attrib_map: The map of attributes to TikZ options. 944 conditions: Optional conditions for the attributes. 945 exceptions: Optional exceptions for the attributes. 946 947 Returns: 948 list: The TikZ options as a list. 949 """ 950 skip = ["marker_color", "fill_color"] 951 tikz_way = {'line_width':LineWidth, 'line_dash_array':LineDashArray} 952 if exceptions: 953 skip += exceptions 954 d_converters = { 955 "line_color": color2tikz, 956 "fill_color": color2tikz, 957 "draw": color2tikz, 958 "line_dash_array": get_dash_pattern, 959 } 960 options = [] 961 for attrib in attrib_list: 962 if attrib not in attrib_map: 963 continue 964 if conditions and attrib in conditions and not conditions[attrib]: 965 continue 966 if attrib in tikz_way: 967 value = getattr(sketch, attrib) 968 if isinstance(value, tikz_way[attrib]): 969 option = value.value 970 options.append(option) 971 continue 972 if isinstance(value, str): 973 if value in tikz_way[attrib]: 974 options.append(value) 975 continue 976 977 tikz_attrib = attrib_map[attrib] 978 if hasattr(sketch, attrib): 979 value = getattr(sketch, attrib) 980 if value is not None and tikz_attrib in list(attrib_map.values()): 981 if attrib in ['smooth']: # smooth is a boolean 982 if value: 983 options.append("smooth") 984 985 elif attrib in skip: 986 value = color2tikz(getattr(sketch, attrib)) 987 options.append(f"{tikz_attrib}={value}") 988 989 elif value != tikz_defaults[tikz_attrib]: 990 if attrib in d_converters: 991 value = d_converters[attrib](value) 992 options.append(f"{tikz_attrib}={value}") 993 994 return options
Converts the attributes of a sketch to TikZ options.
Arguments:
- sketch: The sketch object.
- attrib_list: The list of attributes to convert.
- attrib_map: The map of attributes to TikZ options.
- conditions: Optional conditions for the attributes.
- exceptions: Optional exceptions for the attributes.
Returns:
list: The TikZ options as a list.
997def get_line_style_options(sketch, exceptions=None): 998 """Returns the options for the line style. 999 1000 Args: 1001 sketch: The sketch object. 1002 exceptions: Optional exceptions for the line style options. 1003 1004 Returns: 1005 list: The line style options as a list. 1006 """ 1007 attrib_map = { 1008 "line_color": "color", 1009 "line_width": "line width", 1010 "line_dash_array": "dash pattern", 1011 "line_cap": "line cap", 1012 "line_join": "line join", 1013 "line_miter_limit": "miter limit", 1014 "line_dash_phase": "dash phase", 1015 "line_alpha": "draw opacity", 1016 "smooth": "smooth", 1017 "fillet_radius": "rounded corners", 1018 } 1019 attribs = list(line_style_map.keys()) 1020 if sketch.stroke: 1021 if exceptions and "draw_fillets" not in exceptions: 1022 conditions = {"fillet_radius": sketch.draw_fillets} 1023 else: 1024 conditions = None 1025 if sketch.line_alpha not in [None, 1]: 1026 sketch.line_alpha = sketch.line_alpha 1027 elif sketch.alpha not in [None, 1]: 1028 sketch.fill_alpha = sketch.alpha 1029 else: 1030 attribs.remove("line_alpha") 1031 if not sketch.smooth: 1032 attribs.remove("smooth") 1033 res = sg_to_tikz(sketch, attribs, attrib_map, conditions, exceptions) 1034 else: 1035 res = [] 1036 1037 return res
Returns the options for the line style.
Arguments:
- sketch: The sketch object.
- exceptions: Optional exceptions for the line style options.
Returns:
list: The line style options as a list.
1040def get_fill_style_options(sketch, exceptions=None, frame=False): 1041 """Returns the options for the fill style. 1042 1043 Args: 1044 sketch: The sketch object. 1045 exceptions: Optional exceptions for the fill style options. 1046 frame: Optional flag for frame fill style. 1047 1048 Returns: 1049 list: The fill style options as a list. 1050 """ 1051 attrib_map = { 1052 "fill_color": "fill", 1053 "fill_alpha": "fill opacity", 1054 #'fill_mode': 'even odd rule', 1055 "blend_mode": "blend mode", 1056 "frame_back_color": "fill", 1057 } 1058 attribs = list(shape_style_map.keys()) 1059 if sketch.fill_alpha not in [None, 1]: 1060 sketch.fill_alpha = sketch.fill_alpha 1061 elif sketch.alpha not in [None, 1]: 1062 sketch.fill_alpha = sketch.alpha 1063 else: 1064 attribs.remove("fill_alpha") 1065 if sketch.fill and not sketch.back_style == BackStyle.PATTERN: 1066 res = sg_to_tikz(sketch, attribs, attrib_map, exceptions=exceptions) 1067 if frame: 1068 res = [f"fill = {color2tikz(getattr(sketch, 'frame_back_color'))}"] + res 1069 else: 1070 res = [] 1071 1072 return res
Returns the options for the fill style.
Arguments:
- sketch: The sketch object.
- exceptions: Optional exceptions for the fill style options.
- frame: Optional flag for frame fill style.
Returns:
list: The fill style options as a list.
1075def get_axis_shading_colors(sketch): 1076 """Returns the shading colors for the axis. 1077 1078 Args: 1079 sketch: The sketch object. 1080 1081 Returns: 1082 str: The shading colors for the axis. 1083 """ 1084 def get_color(color, color_key): 1085 if isinstance(color, Color): 1086 res = color2tikz(color) 1087 else: 1088 res = defaults[color_key] 1089 1090 return res 1091 1092 left = get_color(sketch.shade_left_color, "shade_left_color") 1093 right = get_color(sketch.shade_right_color, "shade_right_color") 1094 top = get_color(sketch.shade_top_color, "shade_top_color") 1095 bottom = get_color(sketch.shade_bottom_color, "shade_bottom_color") 1096 middle = get_color(sketch.shade_middle_color, "shade_middle_color") 1097 1098 axis_colors = { 1099 ShadeType.AXIS_BOTTOM_MIDDLE: f"bottom color={bottom}, middle color={middle}", 1100 ShadeType.AXIS_LEFT_MIDDLE: f"left color={left}, middle color={middle}", 1101 ShadeType.AXIS_RIGHT_MIDDLE: f"right color={right}, middle color={middle}", 1102 ShadeType.AXIS_TOP_MIDDLE: f"top color={top}, middle color={middle}", 1103 ShadeType.AXIS_LEFT_RIGHT: f"left color={left}, right color={right}", 1104 ShadeType.AXIS_TOP_BOTTOM: f"top color={top}, bottom color={bottom}", 1105 } 1106 1107 res = axis_colors[sketch.shade_type] 1108 return res
Returns the shading colors for the axis.
Arguments:
- sketch: The sketch object.
Returns:
str: The shading colors for the axis.
1111def get_bilinear_shading_colors(sketch): 1112 """Returns the shading colors for the bilinear shading. 1113 1114 Args: 1115 sketch: The sketch object. 1116 1117 Returns: 1118 str: The shading colors for the bilinear shading. 1119 """ 1120 res = [] 1121 if sketch.shade_upper_left_color: 1122 res.append(f"upper left = {color2tikz(sketch.shade_upper_left_color)}") 1123 if sketch.shade_upper_right_color: 1124 res.append(f"upper right = {color2tikz(sketch.shade_upper_right_color)}") 1125 if sketch.shade_lower_left_color: 1126 res.append(f"lower left = {color2tikz(sketch.shade_lower_left_color)}") 1127 if sketch.shade_lower_right_color: 1128 res.append(f"lower right = {color2tikz(sketch.shade_lower_right_color)}") 1129 1130 return ", ".join(res)
Returns the shading colors for the bilinear shading.
Arguments:
- sketch: The sketch object.
Returns:
str: The shading colors for the bilinear shading.
1133def get_radial_shading_colors(sketch): 1134 """Returns the shading colors for the radial shading. 1135 1136 Args: 1137 sketch: The sketch object. 1138 1139 Returns: 1140 str: The shading colors for the radial shading. 1141 """ 1142 res = [] 1143 if sketch.shade_type == ShadeType.RADIAL_INNER: 1144 res.append(f"inner color = {color2tikz(sketch.shade_inner_color)}") 1145 elif sketch.shade_type == ShadeType.RADIAL_OUTER: 1146 res.append(f"outer color = {color2tikz(sketch.shade_outer_color)}") 1147 elif sketch.shade_type == ShadeType.RADIAL_INNER_OUTER: 1148 res.append(f"inner color = {color2tikz(sketch.shade_inner_color)}") 1149 res.append(f"outer color = {color2tikz(sketch.shade_outer_color)}") 1150 1151 return ", ".join(res)
Returns the shading colors for the radial shading.
Arguments:
- sketch: The sketch object.
Returns:
str: The shading colors for the radial shading.
1170def get_shading_options(sketch): 1171 """Returns the options for the shading. 1172 1173 Args: 1174 sketch: The sketch object. 1175 1176 Returns: 1177 list: The shading options as a list. 1178 """ 1179 shade_type = sketch.shade_type 1180 if shade_type in axis_shading_types: 1181 res = get_axis_shading_colors(sketch) 1182 if sketch.shade_axis_angle: 1183 res += f", shading angle={sketch.shade_axis_angle}" 1184 elif shade_type == ShadeType.BILINEAR: 1185 res = get_bilinear_shading_colors(sketch) 1186 elif shade_type in radial_shading_types: 1187 res = get_radial_shading_colors(sketch) 1188 elif shade_type == ShadeType.BALL: 1189 res = f"ball color = {color2tikz(sketch.shade_ball_color)}" 1190 elif shade_type == ShadeType.COLORWHEEL: 1191 res = "shading=color wheel" 1192 elif shade_type == ShadeType.COLORWHEEL_BLACK: 1193 res = "shading=color wheel black center" 1194 elif shade_type == ShadeType.COLORWHEEL_WHITE: 1195 res = "shading=color wheel white center" 1196 1197 return [res]
Returns the options for the shading.
Arguments:
- sketch: The sketch object.
Returns:
list: The shading options as a list.
1200def get_pattern_options(sketch): 1201 """Returns the options for the patterns. 1202 1203 Args: 1204 sketch: The sketch object. 1205 1206 Returns: 1207 list: The pattern options as a list. 1208 """ 1209 pattern_type = sketch.pattern_type 1210 if pattern_type: 1211 distance = sketch.pattern_distance 1212 options = f"pattern={{{pattern_type}[distance={distance}, " 1213 angle = degrees(sketch.pattern_angle) 1214 if angle: 1215 options += f"angle={angle}, " 1216 line_width = sketch.pattern_line_width 1217 if line_width: 1218 options += f"line width={line_width}, " 1219 x_shift = sketch.pattern_x_shift 1220 if x_shift: 1221 options += f"xshift={x_shift}, " 1222 y_shift = sketch.pattern_y_shift 1223 if y_shift: 1224 options += f"yshift={y_shift}, " 1225 if pattern_type in ["Stars", "Dots"]: 1226 radius = sketch.pattern_radius 1227 if radius: 1228 options += f"radius={radius}, " 1229 if pattern_type == "Stars": 1230 points = sketch.pattern_points 1231 if points: 1232 options += f"points={points}, " 1233 options = options.strip() 1234 if options.endswith(","): 1235 options = options[:-1] 1236 options += "]" 1237 color = sketch.pattern_color 1238 if color and color != sg.black: 1239 options += f", pattern color={color2tikz(color)}, " 1240 1241 options += "}" 1242 res = [options] 1243 else: 1244 res = [] 1245 1246 return res
Returns the options for the patterns.
Arguments:
- sketch: The sketch object.
Returns:
list: The pattern options as a list.
1249def get_marker_options(sketch): 1250 """Returns the options for the markers. 1251 1252 Args: 1253 sketch: The sketch object. 1254 1255 Returns: 1256 list: The marker options as a list. 1257 """ 1258 attrib_map = { 1259 # 'marker': 'mark', 1260 "marker_size": "mark size", 1261 "marker_angle": "rotate", 1262 # 'fill_color': 'color', 1263 "marker_color": "color", 1264 "marker_fill": "fill", 1265 "marker_opacity": "opacity", 1266 "marker_repeat": "mark repeat", 1267 "marker_phase": "mark phase", 1268 "marker_tension": "tension", 1269 "marker_line_width": "line width", 1270 "marker_line_style": "style", 1271 # 'line_color': 'line color', 1272 } 1273 # if mark_stroke is false make line color same as fill color 1274 if sketch.draw_markers: 1275 res = sg_to_tikz(sketch, marker_style_map.keys(), attrib_map) 1276 else: 1277 res = [] 1278 1279 return res
Returns the options for the markers.
Arguments:
- sketch: The sketch object.
Returns:
list: The marker options as a list.
1282def draw_shape_sketch_with_indices(sketch, ind): 1283 """Draws a shape sketch with circle markers with index numbers in them. 1284 1285 Args: 1286 sketch: The shape sketch object. 1287 ind: The index. 1288 1289 Returns: 1290 str: The TikZ code for the shape sketch with indices. 1291 """ 1292 begin_scope = get_begin_scope(ind) 1293 body = get_draw(sketch) 1294 if body: 1295 options = get_line_style_options(sketch) 1296 if sketch.fill and sketch.closed: 1297 options += get_fill_style_options(sketch) 1298 if sketch.smooth: 1299 if sketch.closed: 1300 options += ["smooth cycle"] 1301 else: 1302 options += ["smooth"] 1303 options = ", ".join(options) 1304 body += f"[{options}]" 1305 else: 1306 body = "" 1307 vertices = sketch.vertices 1308 vertices = [str(x) for x in vertices] 1309 str_lines = [vertices[0] + "node{0}"] 1310 n = len(vertices) 1311 for i, vertice in enumerate(vertices[1:]): 1312 if (i + 1) % 6 == 0: 1313 if i == n - 1: 1314 str_lines.append(f" -- {vertice} node{{{i+1}}}\n") 1315 else: 1316 str_lines.append(f"\n\t-- {vertice} node{{{i+1}}}") 1317 else: 1318 str_lines.append(f"-- {vertice} node{{{i+1}}}") 1319 if body: 1320 if sketch.closed: 1321 str_lines.append(" -- cycle;\n") 1322 str_lines.append(";\n") 1323 end_scope = get_end_scope() 1324 1325 return begin_scope + body + "".join(str_lines) + end_scope
Draws a shape sketch with circle markers with index numbers in them.
Arguments:
- sketch: The shape sketch object.
- ind: The index.
Returns:
str: The TikZ code for the shape sketch with indices.
1328def draw_shape_sketch_with_markers(sketch): 1329 """Draws a shape sketch with markers. 1330 1331 Args: 1332 sketch: The shape sketch object. 1333 1334 Returns: 1335 str: The TikZ code for the shape sketch with markers. 1336 """ 1337 # begin_scope = get_begin_scope() 1338 body = get_draw(sketch) 1339 if body: 1340 options = get_line_style_options(sketch) 1341 if sketch.fill and sketch.closed: 1342 options += get_fill_style_options(sketch) 1343 if sketch.smooth and sketch.closed: 1344 options += ["smooth cycle"] 1345 elif sketch.smooth: 1346 options += ["smooth"] 1347 options = ", ".join(options) 1348 if options: 1349 body += f"[{options}]" 1350 else: 1351 body = "" 1352 1353 if sketch.draw_markers: 1354 marker_options = ", ".join(get_marker_options(sketch)) 1355 else: 1356 marker_options = "" 1357 1358 vertices = [str(x) for x in sketch.vertices] 1359 1360 str_lines = [vertices[0]] 1361 for i, vertice in enumerate(vertices[1:]): 1362 if (i + 1) % 6 == 0: 1363 str_lines.append(f"\n\t{vertice} ") 1364 else: 1365 str_lines.append(f" {vertice} ") 1366 coordinates = "".join(str_lines) 1367 1368 marker = get_enum_value(MarkerType, sketch.marker_type) 1369 # marker = sketch.marker_type.value 1370 if sketch.markers_only: 1371 markers_only = "only marks ," 1372 else: 1373 markers_only = "" 1374 if sketch.draw_markers and marker_options: 1375 body += ( 1376 f" plot[mark = {marker}, {markers_only}mark options = {{{marker_options}}}] " 1377 f"\ncoordinates {{{coordinates}}};\n" 1378 ) 1379 elif sketch.draw_markers: 1380 body += ( 1381 f" plot[mark = {marker}, {markers_only}] coordinates {{{coordinates}}};\n" 1382 ) 1383 else: 1384 body += f" plot[tension=.5] coordinates {{{coordinates}}};\n" 1385 1386 return body
Draws a shape sketch with markers.
Arguments:
- sketch: The shape sketch object.
Returns:
str: The TikZ code for the shape sketch with markers.
1389def get_begin_scope(ind=None): 1390 """Returns \begin{scope}[every node/.append style=nodestyle{ind}]. 1391 1392 Args: 1393 ind: Optional index for the scope. 1394 1395 Returns: 1396 str: The begin scope string. 1397 """ 1398 if ind is None: 1399 res = "\\begin{scope}[]\n" 1400 else: 1401 res = f"\\begin{{scope}}[every node/.append style=nodestyle{ind}]\n" 1402 1403 return res
Returns egin{scope}[every node/.append style=nodestyle{ind}].
Arguments:
- ind: Optional index for the scope.
Returns:
str: The begin scope string.
1406def get_end_scope(): 1407 """Returns \\end{scope}. 1408 1409 Returns: 1410 str: The end scope string. 1411 """ 1412 return "\\end{scope}\n"
Returns \end{scope}.
Returns:
str: The end scope string.
1414def draw_pattern_sketch(sketch): 1415 """Draws a pattern sketch. 1416 1417 Args: 1418 sketch: The pattern sketch object. 1419 1420 Returns: 1421 str: The TikZ code for the pattern sketch. 1422 """ 1423 begin_scope = "\\begin{scope}" 1424 1425 options = [] 1426 1427 if sketch.back_style == BackStyle.PATTERN and sketch.fill and sketch.closed: 1428 options += get_pattern_options(sketch) 1429 if sketch.stroke: 1430 options += get_line_style_options(sketch) 1431 if sketch.closed and sketch.fill: 1432 options += get_fill_style_options(sketch) 1433 if sketch.smooth: 1434 options += ["smooth"] 1435 if sketch.back_style == BackStyle.SHADING and sketch.fill and sketch.closed: 1436 options += get_shading_options(sketch) 1437 options = ", ".join(options) 1438 if options: 1439 begin_scope += f"[{options}]\n" 1440 end_scope = get_end_scope() 1441 1442 pattern = sketch.pattern 1443 draw = get_draw(sketch) 1444 if not draw: 1445 return "" 1446 all_vertices = sketch.kernel_vertices @ sketch.all_matrices 1447 vertices_list = np.hsplit(all_vertices, sketch.count) 1448 shapes = [] 1449 for vertices in vertices_list: 1450 vertices @= sketch.xform_matrix 1451 vertices = [tuple(vert) for vert in vertices[:,:2].tolist()] 1452 n = len(vertices) 1453 str_lines = [f"{vertices[0]}"] 1454 for i, vertice in enumerate(vertices[1:]): 1455 if (i + 1) % 8 == 0: 1456 if i == n - 1: 1457 str_lines.append(f"-- {vertice} \n") 1458 else: 1459 str_lines.append(f"\n\t-- {vertice} ") 1460 else: 1461 str_lines.append(f"-- {vertice} ") 1462 if sketch.closed: 1463 str_lines.append("-- cycle;\n") 1464 else: 1465 str_lines.append(";\n") 1466 shapes.append(draw + "".join(str_lines)) 1467 1468 1469 return begin_scope + f"[{options}]\n" + "\n".join(shapes) + end_scope
Draws a pattern sketch.
Arguments:
- sketch: The pattern sketch object.
Returns:
str: The TikZ code for the pattern sketch.
1472def draw_sketch(sketch): 1473 """Draws a plain shape sketch. 1474 1475 Args: 1476 sketch: The shape sketch object. 1477 1478 Returns: 1479 str: The TikZ code for the plain shape sketch. 1480 """ 1481 res = get_draw(sketch) 1482 if not res: 1483 return "" 1484 options = [] 1485 1486 if sketch.back_style == BackStyle.PATTERN and sketch.fill and sketch.closed: 1487 options += get_pattern_options(sketch) 1488 if sketch.stroke: 1489 options += get_line_style_options(sketch) 1490 if sketch.closed and sketch.fill: 1491 options += get_fill_style_options(sketch) 1492 if sketch.smooth: 1493 options += ["smooth"] 1494 if sketch.back_style == BackStyle.SHADING and sketch.fill and sketch.closed: 1495 options += get_shading_options(sketch) 1496 options = ", ".join(options) 1497 if options: 1498 res += f"[{options}]" 1499 vertices = sketch.vertices 1500 n = len(vertices) 1501 str_lines = [f"{vertices[0]}"] 1502 for i, vertice in enumerate(vertices[1:]): 1503 if (i + 1) % 8 == 0: 1504 if i == n - 1: 1505 str_lines.append(f"-- {vertice} \n") 1506 else: 1507 str_lines.append(f"\n\t-- {vertice} ") 1508 else: 1509 str_lines.append(f"-- {vertice} ") 1510 if sketch.closed: 1511 str_lines.append("-- cycle;\n") 1512 else: 1513 str_lines.append(";\n") 1514 if res: 1515 res += "".join(str_lines) 1516 else: 1517 res = "".join(str_lines) 1518 return res
Draws a plain shape sketch.
Arguments:
- sketch: The shape sketch object.
Returns:
str: The TikZ code for the plain shape sketch.
1521def draw_tex_sketch(sketch): 1522 """Draws a TeX sketch. 1523 1524 Args: 1525 sketch: The TeX sketch object. 1526 1527 Returns: 1528 str: The TeX code for the TeX sketch. 1529 """ 1530 1531 return sketch.code
Draws a TeX sketch.
Arguments:
- sketch: The TeX sketch object.
Returns:
str: The TeX code for the TeX sketch.
1533def draw_shape_sketch(sketch, ind=None): 1534 """Draws a shape sketch. 1535 1536 Args: 1537 sketch: The shape sketch object. 1538 ind: Optional index for the shape sketch. 1539 1540 Returns: 1541 str: The TikZ code for the shape sketch. 1542 """ 1543 d_subtype_draw = { 1544 sg.Types.ARC_SKETCH: draw_arc_sketch, 1545 sg.Types.BEZIER_SKETCH: draw_bezier_sketch, 1546 sg.Types.CIRCLE_SKETCH: draw_circle_sketch, 1547 sg.Types.ELLIPSE_SKETCH: draw_ellipse_sketch, 1548 sg.Types.LINE_SKETCH: draw_line_sketch, 1549 } 1550 if sketch.subtype in d_subtype_draw: 1551 res = d_subtype_draw[sketch.subtype](sketch) 1552 elif sketch.draw_markers and sketch.marker_type == MarkerType.INDICES: 1553 res = draw_shape_sketch_with_indices(sketch, ind) 1554 elif sketch.draw_markers or sketch.smooth: 1555 res = draw_shape_sketch_with_markers(sketch) 1556 else: 1557 res = draw_sketch(sketch) 1558 1559 return res
Draws a shape sketch.
Arguments:
- sketch: The shape sketch object.
- ind: Optional index for the shape sketch.
Returns:
str: The TikZ code for the shape sketch.
1561def draw_line_sketch(sketch): 1562 """Draws a line sketch. 1563 1564 Args: 1565 sketch: The line sketch object. 1566 1567 Returns: 1568 str: The TikZ code for the line sketch. 1569 """ 1570 begin_scope = get_begin_scope() 1571 res = "\\draw" 1572 exceptions = ["draw_fillets", "fillet_radius", "line_join", "line_miter_limit"] 1573 options = get_line_style_options(sketch, exceptions=exceptions) 1574 1575 start = sketch.vertices[0] 1576 end = sketch.vertices[1] 1577 options = ", ".join(options) 1578 res += f"[{options}]" 1579 res += f" {start[:2]} -- {end[:2]};\n" 1580 end_scope = get_end_scope() 1581 1582 return begin_scope + res + end_scope
Draws a line sketch.
Arguments:
- sketch: The line sketch object.
Returns:
str: The TikZ code for the line sketch.
1585def draw_circle_sketch(sketch): 1586 """Draws a circle sketch. 1587 1588 Args: 1589 sketch: The circle sketch object. 1590 1591 Returns: 1592 str: The TikZ code for the circle sketch. 1593 """ 1594 begin_scope = get_begin_scope() 1595 res = get_draw(sketch) 1596 if not res: 1597 return "" 1598 options = get_line_style_options(sketch) 1599 fill_options = get_fill_style_options(sketch) 1600 options += fill_options 1601 if sketch.smooth: 1602 options += ["smooth"] 1603 options = ", ".join(options) 1604 if options: 1605 res += f"[{options}]" 1606 x, y = sketch.center[:2] 1607 res += f"({x}, {y}) circle ({sketch.radius});\n" 1608 end_scope = get_end_scope() 1609 1610 return begin_scope + res + end_scope
Draws a circle sketch.
Arguments:
- sketch: The circle sketch object.
Returns:
str: The TikZ code for the circle sketch.
1613def draw_rect_sketch(sketch): 1614 """Draws a rectangle sketch. 1615 1616 Args: 1617 sketch: The rectangle sketch object. 1618 1619 Returns: 1620 str: The TikZ code for the rectangle sketch. 1621 """ 1622 begin_scope = get_begin_scope() 1623 res = get_draw(sketch) 1624 if not res: 1625 return "" 1626 options = get_line_style_options(sketch) 1627 fill_options = get_fill_style_options(sketch) 1628 options += fill_options 1629 if sketch.smooth: 1630 options += ["smooth"] 1631 options = ", ".join(options) 1632 res += f"[{options}]" 1633 x, y = sketch.center[:2] 1634 width, height = sketch.width, sketch.height 1635 res += f"({x}, {y}) rectangle ({width}, {height});\n" 1636 end_scope = get_end_scope() 1637 1638 return begin_scope + res + end_scope
Draws a rectangle sketch.
Arguments:
- sketch: The rectangle sketch object.
Returns:
str: The TikZ code for the rectangle sketch.
1640def draw_ellipse_sketch(sketch): 1641 """Draws an ellipse sketch. 1642 1643 Args: 1644 sketch: The ellipse sketch object. 1645 1646 Returns: 1647 str: The TikZ code for the ellipse sketch. 1648 """ 1649 begin_scope = get_begin_scope() 1650 res = get_draw(sketch) 1651 if not res: 1652 return "" 1653 options = get_line_style_options(sketch) 1654 fill_options = get_fill_style_options(sketch) 1655 options += fill_options 1656 if sketch.smooth: 1657 options += ["smooth"] 1658 angle = degrees(sketch.angle) 1659 x, y = sketch.center[:2] 1660 if angle: 1661 options += [f"rotate around= {{{angle}:({x},{y})}}"] 1662 options = ", ".join(options) 1663 res += f"[{options}]" 1664 a = sketch.x_radius 1665 b = sketch.y_radius 1666 1667 res += f"({x}, {y}) ellipse ({a} and {b});\n" 1668 end_scope = get_end_scope() 1669 1670 return begin_scope + res + end_scope
Draws an ellipse sketch.
Arguments:
- sketch: The ellipse sketch object.
Returns:
str: The TikZ code for the ellipse sketch.
1673def draw_arc_sketch(sketch): 1674 """Draws an arc sketch. 1675 1676 Args: 1677 sketch: The arc sketch object. 1678 1679 Returns: 1680 str: The TikZ code for the arc sketch. 1681 """ 1682 res = get_draw(sketch) 1683 if not res: 1684 return "" 1685 if sketch.closed: 1686 options = ["smooth cycle"] 1687 else: 1688 options = ['smooth'] 1689 1690 if sketch.back_style == BackStyle.PATTERN and sketch.fill and sketch.closed: 1691 options += get_pattern_options(sketch) 1692 if sketch.stroke: 1693 options += get_line_style_options(sketch) 1694 if sketch.closed and sketch.fill: 1695 options += get_fill_style_options(sketch) 1696 1697 if sketch.back_style == BackStyle.SHADING and sketch.fill and sketch.closed: 1698 options += get_shading_options(sketch) 1699 options = ", ".join(options) 1700 if options: 1701 res += f"[{options}] plot[tension=.8] coordinates" + "{" 1702 vertices = [round_point(v) for v in sketch.vertices] 1703 n = len(vertices) 1704 str_lines = [f"{vertices[0]}"] 1705 for i, vertice in enumerate(vertices[1:]): 1706 if (i + 1) % 8 == 0: 1707 if i == n - 1: 1708 str_lines.append(f" {vertice} \n") 1709 else: 1710 str_lines.append(f"\n\t {vertice} ") 1711 else: 1712 str_lines.append(f" {vertice} ") 1713 if sketch.closed: 1714 str_lines.append(" cycle;\n") 1715 else: 1716 str_lines.append("};\n") 1717 if res: 1718 res += "".join(str_lines) 1719 else: 1720 res = "".join(str_lines) 1721 return res
Draws an arc sketch.
Arguments:
- sketch: The arc sketch object.
Returns:
str: The TikZ code for the arc sketch.
1723def draw_bezier_sketch(sketch): 1724 """Draws a Bezier curve sketch. 1725 1726 Args: 1727 sketch: The Bezier curve sketch object. 1728 1729 Returns: 1730 str: The TikZ code for the Bezier curve sketch. 1731 """ 1732 begin_scope = get_begin_scope() 1733 res = get_draw(sketch) 1734 if not res: 1735 return "" 1736 options = get_line_style_options(sketch) 1737 options = ", ".join(options) 1738 res += f"[{options}]" 1739 p1, cp1, cp2, p2 = sketch.control_points 1740 x1, y1 = p1[:2] 1741 x2, y2 = cp1[:2] 1742 x3, y3 = cp2[:2] 1743 x4, y4 = p2[:2] 1744 res += f" ({x1}, {y1}) .. controls ({x2}, {y2}) and ({x3}, {y3}) .. ({x4}, {y4});\n" 1745 end_scope = get_end_scope() 1746 1747 return begin_scope + res + end_scope
Draws a Bezier curve sketch.
Arguments:
- sketch: The Bezier curve sketch object.
Returns:
str: The TikZ code for the Bezier curve sketch.
1750def draw_line(line): 1751 """Tikz code for a line. 1752 1753 Args: 1754 line: The line object. 1755 1756 Returns: 1757 str: The TikZ code for the line. 1758 """ 1759 p1 = line.start[:2] 1760 p2 = line.end[:2] 1761 options = [] 1762 if line.line_width is not None: 1763 options.append(line.line_width) 1764 if line.color is not None: 1765 color = color2tikz(line.color) 1766 options.append(color) 1767 if line.dash_array is not None: 1768 options.append(line.dash_array) 1769 # options = [line.width, line.color, line.dash_array, line.cap, line.join] 1770 if line.line_width == 0: 1771 res = f"\\path[{', '.join(options)}] {p1} -- {p2};\n" 1772 else: 1773 res = f"\\draw[{', '.join(options)}] {p1} -- {p2};\n" 1774 1775 return res
Tikz code for a line.
Arguments:
- line: The line object.
Returns:
str: The TikZ code for the line.
1778def is_stroked(shape: Shape) -> bool: 1779 """Returns True if the shape is stroked. 1780 1781 Args: 1782 shape (Shape): The shape object. 1783 1784 Returns: 1785 bool: True if the shape is stroked, False otherwise. 1786 """ 1787 return shape.stroke and shape.line_color is not None and shape.line_width > 0
Returns True if the shape is stroked.
Arguments:
- shape (Shape): The shape object.
Returns:
bool: True if the shape is stroked, False otherwise.