github.com/gotranspile/cxgo@v0.3.7/examples/potrace/README.md (about) 1 # Potrace 2 3 This example will guide you trough the process of converting [Potrace](http://potrace.sourceforge.net/) from C to Go. 4 5 ## TL;DR 6 7 ```bash 8 cd examples/potrace 9 cxgo 10 cd ../../.examples/potrace-go 11 go test ./... 12 xdg-open stanford.pdf 13 ``` 14 15 If the last command fails, open `stanford.pdf` or `stanford.svg` manually. 16 17 This sequence uses our example [config](cxgo.yml) to fetch Potrace source from the Github, 18 convert it with `cxgo` as a library (Potrace can also be build as a binary) and traces 19 an [image](http://potrace.sourceforge.net/img/stanford.pbm) from the official website. 20 You should see a vectorized output image as a result. 21 22 You just converted the C codebase to Go! 23 24 To learn more about this example, read a guide below. 25 26 ## Prerequisites 27 28 This guide assumes that you have `cxgo` and `git` installed. You don't need anything else. 29 30 ## Getting the C source 31 32 To make things easier, we will pull Potrace code from a [Github mirror](https://github.com/skyrpex/potrace). 33 This can be done by setting the corresponding config options: 34 35 ```yaml 36 vcs: https://github.com/skyrpex/potrace.git 37 branch: '1.15' 38 ``` 39 40 We also need to specify the root for C files, which is `src`: 41 42 ```yaml 43 root: ./src 44 ``` 45 46 ## Minimal config 47 48 To get started with the translation, we need to set a few basic options. 49 50 First, set the output directory and package name for Go files: 51 52 ```yaml 53 out: ../../.examples/potrace-go 54 package: gotrace 55 ``` 56 57 It's always a good idea to force a specific int/pointer type size, so you get the same result regardless of the host. 58 Let's set int and pointer size to 8 bytes (64 bit): 59 60 ```yaml 61 int_size: 8 62 ptr_size: 8 63 ``` 64 65 Now, we need to decide what files to convert. If we look in the source folder, you will see files `potracelib.h` and `potracelib.c`. 66 Those sounds like a good starting point for a library. To convert both files (cxgo will pick `.h` file automatically), 67 add the following directives: 68 69 ```yaml 70 files: 71 - name: potracelib.c 72 ``` 73 74 We are ready to test it out. You should have the following file: 75 76 ```yaml 77 vcs: https://github.com/skyrpex/potrace.git 78 branch: '1.15' 79 root: ./src 80 out: ../../.examples/potrace-go 81 package: gotrace 82 83 int_size: 8 84 ptr_size: 8 85 86 files: 87 - name: potracelib.c 88 ``` 89 90 Let's give it a try! 91 92 ```bash 93 cxgo 94 ``` 95 96 Unfortunately, things are not that simple. You would get an error similar to this: 97 98 ``` 99 potracelib.c: parsing failed: /tmp/potrace/src/potracelib.c:113:24: `VERSION`: expected ; 100 ``` 101 102 From here, an iterative process of fixing the compilation starts. 103 Don't be too scared though - it might not be as hard as you think. 104 105 ## Make it compile 106 107 In the previous step we were left with the following error: 108 109 ``` 110 potracelib.c: parsing failed: /tmp/potrace/src/potracelib.c:113:24: `VERSION`: expected ; 111 ``` 112 113 If you look for the usages of `VERSION` is becomes clear that it's set to a specific value by the build system. 114 Since we are just reading the source, that variable is not defined, causing an error. 115 116 We can fix it by adding a custom `#define` directive in the config: 117 118 ```yaml 119 define: 120 - name: VERSION 121 value: '"dev"' 122 ``` 123 124 Running `cxgo` again will succeed this time. Nice! Now we have a first Go file named `potracelib.go`. 125 126 ## Adding more files 127 128 If you check the `potracelib.go` file or try to build it, you will see a lot of Go error complaining about undefined 129 functions. That's not something scary, we just need to convert more files. 130 131 For example, there are a lot of functions named `progress_*`, so we will add `progress.h`. 132 133 Same for other files: 134 - `bm_to_pathlist` is defined in `decompose.c` 135 - `process_path` is defined in `trace.c` 136 - `path_t` type is defined in `curve.c` 137 - etc 138 139 If you keep adding new files until there are no undefined identifiers, you may get the following list: 140 141 ```yaml 142 files: 143 - name: potracelib.c 144 - name: progress.h 145 - name: trace.c 146 - name: decompose.c 147 - name: curve.c 148 - name: bitmap.h 149 - name: bbox.c 150 - name: auxiliary.h 151 ``` 152 153 You may have noticed that there are other kinds of errors, so let's examine them. 154 155 ### Duplicate declarations 156 157 Starting from `auxiliary.go`, you may see that `potrace_dpoint_t` and `dpoint_t` are already defined in a few other places. 158 This is one of the mistakes cxgo might make, and we'll need to remove the duplicates. 159 160 For `potrace_dpoint_t`, it's defined in `auxiliary.go`, `bbox.go` and `potracelib.go` and the definition always looks the same: 161 162 ```go 163 type potrace_dpoint_t potrace_dpoint_s 164 ``` 165 166 Since this looks like a redefinition of the type, let's just ignore it and ask cxgo to use 167 the underlying type (`potrace_dpoint_s`) whenever it sees `potrace_dpoint_t`. 168 169 This can be done by adding a `idents` section to the top level of the config: 170 171 ```yaml 172 idents: 173 - name: potrace_dpoint_t 174 alias: true 175 ``` 176 177 Adding to the top level instead of a specific file ensures that it will be used consistently in every file that has this declaration. 178 179 If you check the result by running `cxgo` again, you will notice that we now have `potrace_dpoint_s` defined in all 3 files. 180 That is the same issue as the last time, but on an underlying type this time: 181 182 ```go 183 type potrace_dpoint_s struct { 184 X float64 185 Y float64 186 } 187 ``` 188 189 The declaration looks good, so let's just remove it from 2 files. Let's say that we want to keep it in `potracelib.go`, 190 but remove in other files. We can use `skip` to achieve this: 191 192 ```yaml 193 idents: 194 - name: potrace_dpoint_t 195 alias: true 196 197 files: 198 - name: potracelib.c 199 - name: progress.h 200 - name: trace.c 201 - name: decompose.c 202 - name: curve.c 203 - name: bitmap.h 204 - name: bbox.c 205 skip: 206 - potrace_dpoint_s 207 - name: auxiliary.h 208 skip: 209 - potrace_dpoint_s 210 ``` 211 212 We have similar issues with other types (`dpoint_t`, `potrace_path_t`), so let's address them as well: 213 214 ```yaml 215 idents: 216 - name: potrace_dpoint_t 217 alias: true 218 - name: potrace_path_t 219 alias: true 220 - name: dpoint_t 221 alias: true 222 223 files: 224 - name: potracelib.c 225 - name: progress.h 226 - name: trace.c 227 - name: decompose.c 228 - name: curve.c 229 skip: 230 - potrace_path_s 231 - name: bitmap.h 232 - name: bbox.c 233 skip: 234 - potrace_dpoint_s 235 - name: auxiliary.h 236 skip: 237 - potrace_dpoint_s 238 ``` 239 240 This should have solved the duplicate type declarations. But we still have some work to do. 241 242 ### Function name collisions 243 244 Examining error further, you will notice weird issues with functions named `interval`, `iprod` and `bezier`. 245 246 Specifically, if we look at the declarations of `iprod`, we will notice that those are in fact different functions with the same name: 247 248 ```go 249 // bbox.go 250 func iprod(a potrace_dpoint_s, b potrace_dpoint_s) float64 { 251 return a.X*b.X + a.Y*b.Y 252 } 253 254 // trace.go 255 func iprod(p0 potrace_dpoint_s, p1 potrace_dpoint_s, p2 potrace_dpoint_s) float64 { 256 var ( 257 x1 float64 258 y1 float64 259 x2 float64 260 y2 float64 261 ) 262 x1 = p1.X - p0.X 263 y1 = p1.Y - p0.Y 264 x2 = p2.X - p0.X 265 y2 = p2.Y - p0.Y 266 return x1*x2 + y1*y2 267 } 268 ``` 269 270 To fix this name collision, we can use `idents` configs for a specific file (and keep the other name intact): 271 272 ```yaml 273 - name: trace.c 274 idents: 275 - name: iprod 276 rename: trace_iprod 277 ``` 278 279 This will rename the `iprod` in `trace.go` to `trace_iprod`, while version in `bbox.go` will still be named `iprod`. 280 281 Let's try to do the same for `interval` (in `bbox.go` and `auxiliary.go`): 282 283 ```yaml 284 - name: auxiliary.h 285 skip: 286 - potrace_dpoint_s 287 idents: 288 - name: interval 289 rename: aux_interval 290 ``` 291 292 The declarations are now fixed, but there is another issue now: `trace.go` uses `interval` from `bbox.go`, but expects 293 a signature from `auxiliary.go` (now named `aux_interval`). 294 295 To fix this we can rename the usage of this function in `trace.go` as well: 296 297 ```yaml 298 - name: trace.c 299 idents: 300 - name: iprod 301 rename: trace_iprod 302 - name: interval 303 rename: aux_interval 304 ``` 305 306 This will now work. Lastly, do the same for `bezier` (defined in `bbox.go` and `trace.go`): 307 308 ```yaml 309 - name: trace.c 310 idents: 311 # ... 312 - name: bezier 313 rename: trace_bezier 314 ``` 315 316 We are mostly there! But a few smaller issue remain. 317 318 ### Type conversion issues 319 320 Go and C type systems are slightly different, so you may encounter edge cases not yet recognized by cxgo. 321 322 In those cases, a builtin code replacement function may come in handy. 323 324 In our example, you may find the following function declaration: 325 326 ```go 327 func bm_free(bm *potrace_bitmap_t) { 328 if bm != nil && bm.Map != nil { 329 bm_base(bm) = nil 330 } 331 bm = nil 332 } 333 ``` 334 335 The problematic line is: `bm_base(bm) = nil`. The original code reads as `free(bm_base(bm))`, but cxgo replaces `free` 336 with a `nil` pointer assignment, which is invalid when applied to a result of a function call. 337 338 Those issues will eventually be solved in cxgo itself, but for now we can add a workaround: 339 340 ```yaml 341 - name: bitmap.h 342 replace: 343 - old: 'bm_base(bm) = nil' 344 new: 'bm.Map = nil' 345 ``` 346 347 This will replace all occurrences of `bm_base(bm) = nil` to `bm.Map = nil` in `bitmap.go`. 348 349 Those workarounds are not ideal, but may help to get the project converted without waiting for an upsteam fix. 350 351 ### Undefined types 352 353 Sometimes you may find unused fields or undefined types after the conversion. This is usually due to arcane ways how 354 private fields can be implemented in C. 355 356 In our case, you may see that `potrace_privstate_s` is not defined anywhere, and the only usage of the field with this 357 type is `st.Priv = nil`. We have two options here. 358 359 1. Use the code replacement discussed in the previous section. 360 361 2. Define the missing struct type in a separate Go file. 362 363 The second approach is cleaner, so we will proceed with it. We can either create the file manually, or let cxgo do it: 364 365 ```yaml 366 files: 367 # ... 368 - name: hacks.go 369 content: | 370 package gotrace 371 372 type potrace_privstate_s struct{} 373 ``` 374 375 ## Testing attempt 376 377 We now have a first version that should compile. To be on the same page, the config should now contain the following: 378 379 ```yaml 380 vcs: https://github.com/skyrpex/potrace.git 381 branch: '1.15' 382 root: ./src 383 out: ../../.examples/potrace-go 384 package: gotrace 385 int_size: 8 386 ptr_size: 8 387 define: 388 - name: VERSION 389 value: '"dev"' 390 391 idents: 392 - name: potrace_dpoint_t 393 alias: true 394 - name: potrace_path_t 395 alias: true 396 - name: dpoint_t 397 alias: true 398 399 files: 400 - name: potracelib.c 401 - name: progress.h 402 - name: trace.c 403 idents: 404 - name: iprod 405 rename: trace_iprod 406 - name: interval 407 rename: aux_interval 408 - name: bezier 409 rename: trace_bezier 410 - name: decompose.c 411 - name: curve.c 412 skip: 413 - potrace_path_s 414 - name: bitmap.h 415 replace: 416 - old: 'bm_base(bm) = nil' 417 new: 'bm.Map = nil' 418 - name: bbox.c 419 skip: 420 - potrace_dpoint_s 421 - name: auxiliary.h 422 skip: 423 - potrace_dpoint_s 424 idents: 425 - name: interval 426 rename: aux_interval 427 - name: hacks.go 428 content: | 429 package gotrace 430 431 type potrace_privstate_s struct{} 432 ``` 433 434 We can add a dummy test file to quickly check if we can build the project or not: 435 436 ```yaml 437 files: 438 # ... 439 - name: potrace_test.go 440 content: | 441 package gotrace 442 443 import "testing" 444 445 func TestBuild(t *testing.T) { 446 447 } 448 ``` 449 450 Now `go test -v` in the target directory (`../../.examples/potrace-go`) should pass with no errors. 451 But that's not the real test, of course. Let's write something better: 452 453 ```yaml 454 files: 455 # ... 456 - name: potrace_test.go 457 content: | 458 package gotrace 459 460 import ( 461 "math" 462 "testing" 463 464 "github.com/gotranspile/cxgo/runtime/libc" 465 ) 466 467 func TestPotrace(t *testing.T) { 468 bm := bm_new(64, 64) 469 if bm == nil { 470 t.Fatal(libc.Error()) 471 } 472 *bm.Map = math.MaxUint32 473 474 p := potrace_param_default() 475 st := potrace_trace(p, bm) 476 if st == nil { 477 t.Fatal(libc.Error()) 478 } else if st.Status != 0 { 479 t.Fatal(st.Status) 480 } 481 t.Logf("%+v", st) 482 } 483 ``` 484 485 Here we create a new Potrace bitmap with a size `64x64`, set some values into it, and then trace the bitmap with default 486 parameters. 487 488 You probably noticed that test checks values for `nil` and prints `libc.Error()`. Since C code uses `errno` to pass errors, 489 we should call `libc.Error()` to get the error value in case a function fails by returning `nil`. 490 491 Also, the way we set values into a bitmap is weird. The `bm.Map` is a `*potrace_word`, because `cxgo` currently won't 492 auto-detect slices. Because of this it's hard to set the whole bitmap for testing, so we only set the first word 493 (where each bit is a pixel value). We will address an issues with a slice a bit later in this guide. 494 495 Now, running this test (`go test -v`) should print something like this: 496 497 ``` 498 === RUN TestPotrace 499 potrace_test.go:24: &{Status:0 Plist:0xc0000d40f0 Priv:<nil>} 500 --- PASS: TestPotrace (0.00s) 501 PASS 502 ok gotrace 0.002s 503 ``` 504 505 So it looks like we have some traced paths in `st.Plist`. It means that it actually works! 506 507 But the problem is that we can't quickly check the output, since we need to know how to traverse this path list. 508 So let's convert more files and address issues described above. We can also make the code a bit nicer :) 509 510 ## Handling IO 511 512 Now we need to locate the files related to reading bitmaps from files and writing paths to output files. 513 514 ### Reading bitmaps 515 516 Looks like functions related to reading files are located in `bitmap_io.c`, so let's add it to the config as well. 517 We would also need `bitops.h` since it contains dependencies for the previous file. 518 519 ```yaml 520 files: 521 # ... 522 - name: bitmap_io.c 523 - name: bitops.h 524 ``` 525 526 Running the test should succeed, so we will now be able to read bitmaps from files using `bm_read`. This function only 527 accepts a `stdio.File`, so we need some adapter for it. `cxgo` runtime provides a function for this purpose: `stdio.OpenFrom`. 528 529 Let's extend our test case to read a real bitmap: 530 531 ```go 532 package gotrace 533 534 import ( 535 "io" 536 "io/ioutil" 537 "net/http" 538 "os" 539 "testing" 540 541 "github.com/gotranspile/cxgo/runtime/libc" 542 "github.com/gotranspile/cxgo/runtime/stdio" 543 ) 544 545 func TestPotrace(t *testing.T) { 546 resp, err := http.Get("http://potrace.sourceforge.net/img/stanford.pbm") 547 if err != nil { 548 t.Fatal(err) 549 } 550 defer resp.Body.Close() 551 552 if resp.StatusCode != 200 { 553 t.Fatal(resp.Status) 554 } 555 556 tfile, err := ioutil.TempFile("", "potrace_") 557 if err != nil { 558 t.Fatal(err) 559 } 560 defer func() { 561 _ = tfile.Close() 562 _ = os.Remove(tfile.Name()) 563 }() 564 565 _, err = io.Copy(tfile, resp.Body) 566 if err != nil { 567 t.Fatal(err) 568 } 569 tfile.Seek(0, io.SeekStart) 570 571 var bm *potrace_bitmap_t 572 e := bm_read(stdio.OpenFrom(tfile), 0.5, &bm) 573 if e != 0 { 574 t.Fatal(e) 575 } 576 577 p := potrace_param_default() 578 st := potrace_trace(p, bm) 579 if st == nil { 580 t.Fatal(libc.Error()) 581 } else if st.Status != 0 { 582 t.Fatal(st.Status) 583 } 584 t.Logf("%+v", st) 585 } 586 ``` 587 588 The test is very naive: it downloads an image from the Potrace page, saves it in a temporary file and feeds it into 589 `potrace_trace`. Of course, we could uses pipes to avoid on-disk files, or cache the file instead downloading it each time, 590 but for purposes of this guide we will use the simplest option for illustration purposes. 591 592 ### Writing paths 593 594 Now we need to have a way to save paths generated by `potrace_trace`. Potrace has different backends, so let's pick a few 595 ones that should be the most useful ones: SVG and PDF. 596 597 #### SVG 598 599 First, let's try to add `backend_svg.c` to our config. We'll get a familiar: 600 601 ``` 602 backend_svg.c: parsing failed: /tmp/potrace/src/backend_svg.c:307:31: `POTRACE`: expected , or ) 603 ``` 604 605 This is the same issues as we faced with `VERSION`, so let's add another `define`: 606 607 ```yaml 608 define: 609 - name: VERSION 610 value: '"dev"' 611 - name: POTRACE 612 value: '"potrace"' 613 ``` 614 615 Now the transpilation succeeds, but we see that an `info` variable is referenced, but is not defined anywhere. 616 617 Looking at the files, it seems like there are two conflicting declarations: one from `main.h` and the second one from `mkbitmap.c`. 618 It makes sense, because those globals store Potrace configuration for `potrace` and `mkbitmap` binaries. 619 620 This is really unfortunate, since it would be nice to pass this configuration explicitly to avoid confusion for potential 621 users of our library. 622 623 We can solve it by using `replace`. First, add one of the files (`main.h`), suppress the declaration of a global `info` variable. 624 Then, use `replace` to rewrite Go code and add one more argument to SVG-related functions with the same name as a global. 625 This is a bit involved, but should make the API surface much better. 626 627 So, let's add the `main.h` (without `main.c`): 628 629 ```yaml 630 files: 631 # ... 632 - name: main.h 633 go: backend.go 634 ``` 635 636 Actually, that's all we need here: the header references to `info` as `extern`, and the variable is declared in `main.c`. 637 We are almost done here, but we now need to fix a few more undefined types: 638 639 ```yaml 640 files: 641 # ... 642 - name: trans.c 643 - name: progress_bar.c 644 ``` 645 646 Next, let's start adding replacement directives, starting from the top-level API functions: 647 648 ```yaml 649 files: 650 # ... 651 - name: backend_svg.c 652 replace: 653 - old: 'func page_svg(' 654 new: 'func page_svg(info *info_s, ' 655 - old: 'func page_gimp(' 656 new: 'func page_gimp(info *info_s, ' 657 ``` 658 659 And fix the function calls to them as well: 660 661 ```yaml 662 replace: 663 # ... 664 - old: 'page_svg(fout' 665 new: 'page_svg(info, fout' 666 ``` 667 668 Now, let's repeat this for all other functions in this file that depend on `info`. The result should look like this: 669 670 ```yaml 671 - name: backend_svg.c 672 replace: 673 - old: 'func unit(' 674 new: 'func unit(info *info_s, ' 675 - old: 'func svg_moveto(' 676 new: 'func svg_moveto(info *info_s, ' 677 - old: 'func svg_rmoveto(' 678 new: 'func svg_rmoveto(info *info_s, ' 679 - old: 'func svg_lineto(' 680 new: 'func svg_lineto(info *info_s, ' 681 - old: 'func svg_curveto(' 682 new: 'func svg_curveto(info *info_s, ' 683 - old: 'func svg_path(' 684 new: 'func svg_path(info *info_s, ' 685 - old: 'func svg_jaggy_path(' 686 new: 'func svg_jaggy_path(info *info_s, ' 687 - old: 'func write_paths_opaque(' 688 new: 'func write_paths_opaque(info *info_s, ' 689 - old: 'func write_paths_transparent_rec(' 690 new: 'func write_paths_transparent_rec(info *info_s, ' 691 - old: 'func write_paths_transparent(' 692 new: 'func write_paths_transparent(info *info_s, ' 693 - old: 'func page_svg(' 694 new: 'func page_svg(info *info_s, ' 695 - old: 'func page_gimp(' 696 new: 'func page_gimp(info *info_s, ' 697 - old: 'unit(p)' 698 new: 'unit(info, p)' 699 - old: 'unit(p1)' 700 new: 'unit(info, p1)' 701 - old: 'unit(p2)' 702 new: 'unit(info, p2)' 703 - old: 'unit(p3)' 704 new: 'unit(info, p3)' 705 - old: 'svg_moveto(fout' 706 new: 'svg_moveto(info, fout' 707 - old: 'svg_rmoveto(fout' 708 new: 'svg_rmoveto(info, fout' 709 - old: 'svg_lineto(fout' 710 new: 'svg_lineto(info, fout' 711 - old: 'svg_curveto(fout' 712 new: 'svg_curveto(info, fout' 713 - old: 'svg_jaggy_path(fout' 714 new: 'svg_jaggy_path(info, fout' 715 - old: 'svg_path(fout' 716 new: 'svg_path(info, fout' 717 - old: 'write_paths_opaque(fout' 718 new: 'write_paths_opaque(info, fout' 719 - old: 'write_paths_transparent_rec(fout' 720 new: 'write_paths_transparent_rec(info, fout' 721 - old: 'write_paths_transparent(fout' 722 new: 'write_paths_transparent(info, fout' 723 - old: 'page_svg(fout' 724 new: 'page_svg(info, fout' 725 ``` 726 727 Transpiling it will only show that `backend_s` is undefined. Since it's unused in the code, let's add a stub for it: 728 729 ```yaml 730 - name: hacks.go 731 content: | 732 package gotrace 733 734 type backend_s struct{} 735 type potrace_privstate_s struct{} 736 ``` 737 738 It should now compile again, and we can use new functions in our test: 739 740 ```go 741 package gotrace 742 743 import ( 744 "io" 745 "io/ioutil" 746 "net/http" 747 "os" 748 "testing" 749 750 "github.com/gotranspile/cxgo/runtime/libc" 751 "github.com/gotranspile/cxgo/runtime/stdio" 752 ) 753 754 func TestPotrace(t *testing.T) { 755 resp, err := http.Get("http://potrace.sourceforge.net/img/stanford.pbm") 756 if err != nil { 757 t.Fatal(err) 758 } 759 defer resp.Body.Close() 760 761 if resp.StatusCode != 200 { 762 t.Fatal(resp.Status) 763 } 764 765 tfile, err := ioutil.TempFile("", "potrace_") 766 if err != nil { 767 t.Fatal(err) 768 } 769 defer func() { 770 _ = tfile.Close() 771 _ = os.Remove(tfile.Name()) 772 }() 773 774 _, err = io.Copy(tfile, resp.Body) 775 if err != nil { 776 t.Fatal(err) 777 } 778 tfile.Seek(0, io.SeekStart) 779 780 var bm *potrace_bitmap_t 781 e := bm_read(stdio.OpenFrom(tfile), 0.5, &bm) 782 if e != 0 { 783 t.Fatal(e) 784 } 785 786 p := potrace_param_default() 787 st := potrace_trace(p, bm) 788 if st == nil { 789 t.Fatal(libc.Error()) 790 } else if st.Status != 0 { 791 t.Fatal(st.Status) 792 } 793 var tr trans_t 794 trans_from_rect(&tr, float64(bm.W), float64(bm.H)) 795 bi := &info_s{ 796 Unit: 1, 797 } 798 iinfo := &imginfo_t{ 799 Pixwidth: bm.W, Pixheight: bm.H, 800 Width: float64(bm.W), Height: float64(bm.H), 801 Trans: tr, 802 } 803 804 svgOut, err := os.Create("stanford.svg") 805 if err != nil { 806 t.Fatal(err) 807 } 808 defer svgOut.Close() 809 page_svg(bi, stdio.OpenFrom(svgOut), st.Plist, iinfo) 810 } 811 ``` 812 813 Test should again succeed and generate a `stanford.svg` files that can be viewed in the browser. 814 815 #### PDF 816 817 We promised to have PDF support as well, so let's try to add `backend_pdf.c` as well. 818 819 Of course, we will see similar issues with `info` here as well. 820 821 But, before addressing these, we can also see that some 822 functions have conflicts in those two backends files. We already know how to solve it: 823 824 ```yaml 825 files: 826 # ... 827 - name: backend_pdf.c 828 idents: 829 - name: unit 830 rename: pdf_unit 831 - name: ship 832 rename: pdf_ship 833 ``` 834 835 Now, let's again add replacements for `info`: 836 837 ```yaml 838 - name: backend_pdf.c 839 # ... 840 replace: 841 - old: 'func render0(' 842 new: 'func render0(info *info_s, ' 843 - old: 'func render0_opaque(' 844 new: 'func render0_opaque(info *info_s, ' 845 - old: 'func pdf_render(' 846 new: 'func pdf_render(info *info_s, ' 847 - old: 'func pdf_callbacks(' 848 new: 'func pdf_callbacks(info *info_s, ' 849 - old: 'func pdf_unit(' 850 new: 'func pdf_unit(info *info_s, ' 851 - old: 'func pdf_coords(' 852 new: 'func pdf_coords(info *info_s, ' 853 - old: 'func pdf_moveto(' 854 new: 'func pdf_moveto(info *info_s, ' 855 - old: 'func pdf_lineto(' 856 new: 'func pdf_lineto(info *info_s, ' 857 - old: 'func pdf_curveto(' 858 new: 'func pdf_curveto(info *info_s, ' 859 - old: 'func pdf_path(' 860 new: 'func pdf_path(info *info_s, ' 861 - old: 'func pdf_pageinit(' 862 new: 'func pdf_pageinit(info *info_s, ' 863 - old: 'func page_pdfpage(' 864 new: 'func page_pdfpage(info *info_s, ' 865 - old: 'func page_pdf(' 866 new: 'func page_pdf(info *info_s, ' 867 - old: 'func init_pdf(' 868 new: 'func init_pdf(info *info_s, ' 869 - old: 'func term_pdf(' 870 new: 'func term_pdf(info *info_s, ' 871 - old: 'render0_opaque(plist)' 872 new: 'render0_opaque(info, plist)' 873 - old: 'render0(plist)' 874 new: 'render0(info, plist)' 875 - old: 'pdf_unit(p' 876 new: 'pdf_unit(info, p' 877 - old: 'pdf_coords(p' 878 new: 'pdf_coords(info, p' 879 - old: 'pdf_moveto(*' 880 new: 'pdf_moveto(info, *' 881 - old: 'pdf_lineto(*' 882 new: 'pdf_lineto(info, *' 883 - old: 'pdf_curveto(*' 884 new: 'pdf_curveto(info, *' 885 - old: 'pdf_callbacks(fout' 886 new: 'pdf_callbacks(info, fout' 887 - old: 'pdf_pageinit(imginfo' 888 new: 'pdf_pageinit(info, imginfo' 889 - old: 'pdf_render(plist' 890 new: 'pdf_render(info, plist' 891 - old: 'pdf_path(&' 892 new: 'pdf_path(info, &' 893 ``` 894 895 We notice that `dummy_xship` and `pdf_xship`. They are defined in `flate.c`, so we add it too. 896 897 Since we don't really need anything from that file except those two functions so let's skip `lzw_xship` that causes more errors. 898 899 ```yaml 900 files: 901 # ... 902 - name: flate.c 903 skip: 904 - lzw_xship 905 ``` 906 907 And finally, let's add a few more lines to our test to generate a PDF as well: 908 909 910 ```go 911 pdfOut, err := os.Create("stanford.pdf") 912 if err != nil { 913 t.Fatal(err) 914 } 915 defer pdfOut.Close() 916 init_pdf(bi, stdio.OpenFrom(pdfOut)) 917 page_pdf(bi, stdio.OpenFrom(pdfOut), st.Plist, iinfo) 918 term_pdf(bi, stdio.OpenFrom(pdfOut)) 919 ``` 920 921 Test should succeed and generate a `stanford.pdf` files that should be more convenient to check. 922 923 ## Improving the code 924 925 At this stage we have a working prototype, but the code might not be as nice as it could be. 926 927 Here are some issues we have: 928 929 - Fixed int types (`int64`) instead of Go `int` 930 - C names for types and functions 931 - Int types where we want bool types 932 - Pointer types where we want to have slices 933 934 We will now show how to address these issues. 935 936 To be on the same page, your config should look like this: 937 938 ```yaml 939 vcs: https://github.com/skyrpex/potrace.git 940 branch: '1.15' 941 root: ./src 942 out: ../../.examples/potrace-go 943 package: gotrace 944 int_size: 8 945 ptr_size: 8 946 define: 947 - name: VERSION 948 value: '"dev"' 949 - name: POTRACE 950 value: '"potrace"' 951 952 idents: 953 - name: potrace_dpoint_t 954 alias: true 955 - name: potrace_path_t 956 alias: true 957 - name: dpoint_t 958 alias: true 959 960 files: 961 - name: potracelib.c 962 - name: progress.h 963 - name: trace.c 964 idents: 965 - name: iprod 966 rename: trace_iprod 967 - name: interval 968 rename: aux_interval 969 - name: bezier 970 rename: trace_bezier 971 - name: decompose.c 972 - name: curve.c 973 skip: 974 - potrace_path_s 975 - name: bitmap.h 976 replace: 977 - old: 'bm_base(bm) = nil' 978 new: 'bm.Map = nil' 979 - name: bbox.c 980 skip: 981 - potrace_dpoint_s 982 - name: auxiliary.h 983 skip: 984 - potrace_dpoint_s 985 idents: 986 - name: interval 987 rename: aux_interval 988 - name: bitmap_io.c 989 - name: bitops.h 990 - name: backend_svg.c 991 replace: 992 - old: 'func unit(' 993 new: 'func unit(info *info_s, ' 994 - old: 'func svg_moveto(' 995 new: 'func svg_moveto(info *info_s, ' 996 - old: 'func svg_rmoveto(' 997 new: 'func svg_rmoveto(info *info_s, ' 998 - old: 'func svg_lineto(' 999 new: 'func svg_lineto(info *info_s, ' 1000 - old: 'func svg_curveto(' 1001 new: 'func svg_curveto(info *info_s, ' 1002 - old: 'func svg_path(' 1003 new: 'func svg_path(info *info_s, ' 1004 - old: 'func svg_jaggy_path(' 1005 new: 'func svg_jaggy_path(info *info_s, ' 1006 - old: 'func write_paths_opaque(' 1007 new: 'func write_paths_opaque(info *info_s, ' 1008 - old: 'func write_paths_transparent_rec(' 1009 new: 'func write_paths_transparent_rec(info *info_s, ' 1010 - old: 'func write_paths_transparent(' 1011 new: 'func write_paths_transparent(info *info_s, ' 1012 - old: 'func page_svg(' 1013 new: 'func page_svg(info *info_s, ' 1014 - old: 'func page_gimp(' 1015 new: 'func page_gimp(info *info_s, ' 1016 - old: 'unit(p)' 1017 new: 'unit(info, p)' 1018 - old: 'unit(p1)' 1019 new: 'unit(info, p1)' 1020 - old: 'unit(p2)' 1021 new: 'unit(info, p2)' 1022 - old: 'unit(p3)' 1023 new: 'unit(info, p3)' 1024 - old: 'svg_moveto(fout' 1025 new: 'svg_moveto(info, fout' 1026 - old: 'svg_rmoveto(fout' 1027 new: 'svg_rmoveto(info, fout' 1028 - old: 'svg_lineto(fout' 1029 new: 'svg_lineto(info, fout' 1030 - old: 'svg_curveto(fout' 1031 new: 'svg_curveto(info, fout' 1032 - old: 'svg_jaggy_path(fout' 1033 new: 'svg_jaggy_path(info, fout' 1034 - old: 'svg_path(fout' 1035 new: 'svg_path(info, fout' 1036 - old: 'write_paths_opaque(fout' 1037 new: 'write_paths_opaque(info, fout' 1038 - old: 'write_paths_transparent_rec(fout' 1039 new: 'write_paths_transparent_rec(info, fout' 1040 - old: 'write_paths_transparent(fout' 1041 new: 'write_paths_transparent(info, fout' 1042 - old: 'page_svg(fout' 1043 new: 'page_svg(info, fout' 1044 - name: flate.c 1045 skip: 1046 - lzw_xship 1047 - name: backend_pdf.c 1048 idents: 1049 - name: unit 1050 rename: pdf_unit 1051 - name: ship 1052 rename: pdf_ship 1053 replace: 1054 - old: 'func render0(' 1055 new: 'func render0(info *info_s, ' 1056 - old: 'func render0_opaque(' 1057 new: 'func render0_opaque(info *info_s, ' 1058 - old: 'func pdf_render(' 1059 new: 'func pdf_render(info *info_s, ' 1060 - old: 'func pdf_callbacks(' 1061 new: 'func pdf_callbacks(info *info_s, ' 1062 - old: 'func pdf_unit(' 1063 new: 'func pdf_unit(info *info_s, ' 1064 - old: 'func pdf_coords(' 1065 new: 'func pdf_coords(info *info_s, ' 1066 - old: 'func pdf_moveto(' 1067 new: 'func pdf_moveto(info *info_s, ' 1068 - old: 'func pdf_lineto(' 1069 new: 'func pdf_lineto(info *info_s, ' 1070 - old: 'func pdf_curveto(' 1071 new: 'func pdf_curveto(info *info_s, ' 1072 - old: 'func pdf_path(' 1073 new: 'func pdf_path(info *info_s, ' 1074 - old: 'func pdf_pageinit(' 1075 new: 'func pdf_pageinit(info *info_s, ' 1076 - old: 'func page_pdfpage(' 1077 new: 'func page_pdfpage(info *info_s, ' 1078 - old: 'func page_pdf(' 1079 new: 'func page_pdf(info *info_s, ' 1080 - old: 'func init_pdf(' 1081 new: 'func init_pdf(info *info_s, ' 1082 - old: 'func term_pdf(' 1083 new: 'func term_pdf(info *info_s, ' 1084 - old: 'render0_opaque(plist)' 1085 new: 'render0_opaque(info, plist)' 1086 - old: 'render0(plist)' 1087 new: 'render0(info, plist)' 1088 - old: 'pdf_unit(p' 1089 new: 'pdf_unit(info, p' 1090 - old: 'pdf_coords(p' 1091 new: 'pdf_coords(info, p' 1092 - old: 'pdf_moveto(*' 1093 new: 'pdf_moveto(info, *' 1094 - old: 'pdf_lineto(*' 1095 new: 'pdf_lineto(info, *' 1096 - old: 'pdf_curveto(*' 1097 new: 'pdf_curveto(info, *' 1098 - old: 'pdf_callbacks(fout' 1099 new: 'pdf_callbacks(info, fout' 1100 - old: 'pdf_pageinit(imginfo' 1101 new: 'pdf_pageinit(info, imginfo' 1102 - old: 'pdf_render(plist' 1103 new: 'pdf_render(info, plist' 1104 - old: 'pdf_path(&' 1105 new: 'pdf_path(info, &' 1106 - name: main.h 1107 go: backend.go 1108 - name: trans.c 1109 - name: progress_bar.c 1110 - name: hacks.go 1111 content: | 1112 package gotrace 1113 1114 type backend_s struct{} 1115 type potrace_privstate_s struct{} 1116 - name: potrace_test.go 1117 content: | 1118 package gotrace 1119 1120 import ( 1121 "io" 1122 "io/ioutil" 1123 "net/http" 1124 "os" 1125 "testing" 1126 1127 "github.com/gotranspile/cxgo/runtime/libc" 1128 "github.com/gotranspile/cxgo/runtime/stdio" 1129 ) 1130 1131 func TestPotrace(t *testing.T) { 1132 resp, err := http.Get("http://potrace.sourceforge.net/img/stanford.pbm") 1133 if err != nil { 1134 t.Fatal(err) 1135 } 1136 defer resp.Body.Close() 1137 1138 if resp.StatusCode != 200 { 1139 t.Fatal(resp.Status) 1140 } 1141 1142 tfile, err := ioutil.TempFile("", "potrace_") 1143 if err != nil { 1144 t.Fatal(err) 1145 } 1146 defer func() { 1147 _ = tfile.Close() 1148 _ = os.Remove(tfile.Name()) 1149 }() 1150 1151 _, err = io.Copy(tfile, resp.Body) 1152 if err != nil { 1153 t.Fatal(err) 1154 } 1155 tfile.Seek(0, io.SeekStart) 1156 1157 var bm *potrace_bitmap_t 1158 e := bm_read(stdio.OpenFrom(tfile), 0.5, &bm) 1159 if e != 0 { 1160 t.Fatal(e) 1161 } 1162 1163 p := potrace_param_default() 1164 st := potrace_trace(p, bm) 1165 if st == nil { 1166 t.Fatal(libc.Error()) 1167 } else if st.Status != 0 { 1168 t.Fatal(st.Status) 1169 } 1170 var tr trans_t 1171 trans_from_rect(&tr, float64(bm.W), float64(bm.H)) 1172 bi := &info_s{ 1173 Unit: 1, 1174 } 1175 iinfo := &imginfo_t{ 1176 Pixwidth: bm.W, Pixheight: bm.H, 1177 Width: float64(bm.W), Height: float64(bm.H), 1178 Trans: tr, 1179 } 1180 1181 svgOut, err := os.Create("stanford.svg") 1182 if err != nil { 1183 t.Fatal(err) 1184 } 1185 defer svgOut.Close() 1186 page_svg(bi, stdio.OpenFrom(svgOut), st.Plist, iinfo) 1187 1188 pdfOut, err := os.Create("stanford.pdf") 1189 if err != nil { 1190 t.Fatal(err) 1191 } 1192 defer pdfOut.Close() 1193 init_pdf(bi, stdio.OpenFrom(pdfOut)) 1194 page_pdf(bi, stdio.OpenFrom(pdfOut), st.Plist, iinfo) 1195 term_pdf(bi, stdio.OpenFrom(pdfOut)) 1196 } 1197 ``` 1198 1199 ### Using Go ints 1200 1201 Since we can now ensure that the code works correctly, we can experiment with the types. `cxgo` allows using Go `int` 1202 instead of fixed-size ints generated by default. Add the following line to the config: 1203 1204 ```yaml 1205 use_go_int: true 1206 ``` 1207 1208 Test should lead to exactly the same result. For some projects it might be false, so use this option with a caution. 1209 1210 ### Better names 1211 1212 `cxgo` won't rename functions and types by default, but we can instruct it to do so, if desired. 1213 1214 For example, let's rename the functions use in tests and types in `potracelib.go`. We will also remove duplicates 1215 like `type_t` and `type_s` to make the code cleaner. 1216 1217 ```yaml 1218 idents: 1219 # rename identifiers 1220 - name: potrace_progress_s 1221 rename: Progress 1222 - name: potrace_param_s 1223 rename: Param 1224 - name: potrace_word 1225 rename: Word 1226 - name: potrace_bitmap_s 1227 rename: Bitmap 1228 - name: potrace_dpoint_s 1229 rename: DPoint 1230 - name: potrace_curve_s 1231 rename: Curve 1232 - name: potrace_path_s 1233 rename: Path 1234 - name: potrace_state_s 1235 rename: State 1236 - name: trans_s 1237 rename: Trans 1238 - name: imginfo_s 1239 rename: ImgInfo 1240 - name: info_s 1241 rename: BackendInfo 1242 - name: dim_s 1243 rename: Dim 1244 - name: potrace_param_default 1245 rename: ParamDefault 1246 - name: potrace_trace 1247 rename: Trace 1248 # cleanup duplicate types 1249 - name: potrace_dpoint_t 1250 alias: true 1251 - name: potrace_path_t 1252 alias: true 1253 - name: dpoint_t 1254 alias: true 1255 - name: potrace_progress_t 1256 alias: true 1257 - name: potrace_param_t 1258 alias: true 1259 - name: potrace_bitmap_t 1260 alias: true 1261 - name: potrace_curve_t 1262 alias: true 1263 - name: potrace_state_t 1264 alias: true 1265 - name: trans_t 1266 alias: true 1267 - name: imginfo_t 1268 alias: true 1269 - name: info_t 1270 alias: true 1271 - name: dim_t 1272 alias: true 1273 ``` 1274 1275 Also, don't forget to adjust test file and the `replace` directives. We can continue improving names in other files, 1276 but there are other issues to address as well. 1277 1278 ### Using Go bools 1279 1280 C code doesn't always use `bool` where it should. `cxgo` has a way to give a hint about those fields/arguments. 1281 1282 For example, in our `BackendInfo` struct type (former `info_s`) we have a name called `Debug`. If we check usages, 1283 it's clear that it's used as a boolean value. 1284 1285 We can add the following to the `BackendInfo` definition to force `bool` type here: 1286 1287 ```yaml 1288 idents: 1289 # ... 1290 - name: info_s 1291 rename: BackendInfo 1292 fields: 1293 - name: debug 1294 type: bool 1295 ``` 1296 1297 Note that `debug` is a C name, not a name that you see in Go (`Debug`). 1298 1299 We can do the same for other fields like `Compress`, `Opaque`, etc. 1300 1301 ### Using slices 1302 1303 C doesn't have a notion of slices and unfortunately `cxgo` is not smart enough to detect those automatically. 1304 But again, we have a way to give it a hint for a specific struct field or function argument. 1305 1306 We can start with promoting `Bitmap.Map` (`potrace_bitmap_t.map`) to a slice: 1307 1308 ```yaml 1309 idents: 1310 # ... 1311 - name: potrace_bitmap_s 1312 rename: Bitmap 1313 fields: 1314 - name: map 1315 type: slice 1316 ``` 1317 1318 Unfortunately this won't compile: there are some unusual slice usages in the code. 1319 1320 First, this one: 1321 1322 ```go 1323 bm.Map = ([]Word)((*Word)(libc.Calloc(1, int(size)))) 1324 ``` 1325 1326 In fact, `cxgo` supports converting `calloc` to `make`, but it gots confused by `size` variable instead of `sizeof(T)`. 1327 We can help it with `replace` (on `bitmap.h`): 1328 1329 ```yaml 1330 files: 1331 # ... 1332 - name: bitmap.h 1333 replace: 1334 - old: 'bm_base(bm) = nil' 1335 new: 'bm.Map = nil' 1336 - old: 'bm.Map = ([]Word)((*Word)(libc.Calloc(1, int(size))))' 1337 new: 'bm.Map = make([]Word, uintptr(size)/unsafe.Sizeof(Word(0)))' 1338 ``` 1339 1340 The second usage is the following (in `bm_flip`): 1341 1342 ```yaml 1343 bm.Map = ([]Word)(&bm.Map[int64(bm.H-1)*int64(bm.Dy)]) 1344 bm.Dy = -dy 1345 ``` 1346 1347 Potrace author doe a trick to flip the bitmap: he sets a pointer to the last element instead of the first and makes the 1348 stride (row offset multiplier) negative. This way the indexing will automatically go backward. 1349 1350 Although it's a nice trick, Go won't allow this trickery on slices. So we have to disable this function and reimplement 1351 it later in a different way (e.g. checking `Dy` sign and change how we index the slice). 1352 1353 Disabling can be done with `skip` on `bitmap.h`, and we will add a stub for into `hacks.go` since it's actually use in the code: 1354 1355 ```yaml 1356 files: 1357 # ... 1358 - name: bitmap.h 1359 skip: 1360 - bm_flip 1361 # ... 1362 - name: hacks.go 1363 content: | 1364 package gotrace 1365 1366 // C flips bitmaps by using negative bitmap strides, which we cannot represent in Go with slices 1367 1368 func bm_flip(bm *Bitmap) { 1369 // TODO: implement 1370 } 1371 1372 type backend_s struct{} 1373 type potrace_privstate_s struct{} 1374 ``` 1375 1376 And finally, the last issue is: 1377 1378 ```go 1379 newmap = (*Word)(libc.Realloc(unsafe.Pointer(&bm.Map[0]), int(newsize))) 1380 if newmap == nil { 1381 goto error 1382 } 1383 bm.Map = ([]Word)(newmap) 1384 ``` 1385 1386 `cxgo` doesn't translate `realloc` yet for slices, and even if it did, the local variable `newmap` still has a pointer type. 1387 So we need to make at least two replacements here (plus one cosmetic): 1388 1389 ```yaml 1390 replace: 1391 # ... 1392 - old: 'newmap = (*Word)(libc.Realloc(unsafe.Pointer(&bm.Map[0]), int(newsize)))' 1393 new: 'newmap = make([]Word, uintptr(newsize)/unsafe.Sizeof(Word(0))); copy(newmap, bm.Map)' 1394 - old: 'newmap *Word' 1395 new: 'newmap []Word' 1396 - old: 'bm.Map = ([]Word)(newmap)' 1397 new: 'bm.Map = newmap' 1398 ``` 1399 1400 This time it should compile, and the tests should still pass.