• Snipping the ears

    From luser droog@21:1/5 to Mark Carroll on Mon Jul 29 23:18:39 2019
    On Wednesday, July 24, 2019 at 2:50:05 AM UTC-5, Mark Carroll wrote:
    On 24 Jul 2019, luser droog wrote:

    When running through the path, transforming one 'lineto' or 'curveto' at
    a time, we need a little surrounding context to get all the interesting vectors that contribute to the result.

    I'm afraid that with this kind of thing I always did flattenpath then
    just had lineto's to deal with.


    I tried that first of course, but it leads to these ugly artifacts at the discontinuities. I tried flattening and then using the outgoing vectors
    from each point. Then I tried the incoming vectors. Then I tried taking clusters of 3, 4 or 5 consecutive points and averaging the interstitial vectors. All of these led to ears.

    But there may have been errors in my implementation which concealed
    positive (or promising) results. Thinking over it, using 3 consecutive
    points should have been pretty close if it were done correctly.

    But I have a new idea which really seems like it should work.
    For lineto segments average the incoming and outgoing vectors as
    described earlier. But for curveto segments, if I do a de Casteljau
    subdivision just once, then I get a middle point and two tangent
    vectors at that point for cheap. If it doubles the number of curves,
    that's not too terrible I think.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From luser droog@21:1/5 to luser droog on Tue Aug 6 22:05:50 2019
    On Tuesday, July 30, 2019 at 1:18:40 AM UTC-5, luser droog wrote:
    On Wednesday, July 24, 2019 at 2:50:05 AM UTC-5, Mark Carroll wrote:
    On 24 Jul 2019, luser droog wrote:

    When running through the path, transforming one 'lineto' or 'curveto' at a time, we need a little surrounding context to get all the interesting vectors that contribute to the result.

    I'm afraid that with this kind of thing I always did flattenpath then
    just had lineto's to deal with.


    I tried that first of course, but it leads to these ugly artifacts at the discontinuities. I tried flattening and then using the outgoing vectors
    from each point. Then I tried the incoming vectors. Then I tried taking clusters of 3, 4 or 5 consecutive points and averaging the interstitial vectors. All of these led to ears.

    But there may have been errors in my implementation which concealed
    positive (or promising) results. Thinking over it, using 3 consecutive
    points should have been pretty close if it were done correctly.

    But I have a new idea which really seems like it should work.
    For lineto segments average the incoming and outgoing vectors as
    described earlier. But for curveto segments, if I do a de Casteljau subdivision just once, then I get a middle point and two tangent
    vectors at that point for cheap. If it doubles the number of curves,
    that's not too terrible I think.

    At long last I've implemented all of these ideas without errors this time.
    And it still makes ears. But I think this is good progress.

    I found some code in Don Lancaster's pages to measure the length of a curve.
    I think I can use that to determine if my new offset curve is bigger or
    smaller than the original. And that should tell me whether we're inside or outside of a bend.

    But a numerical/conceptual problem keeps cropping up. Whenever I try to
    run through a series of line segments to get the angular change, I don't
    know how to bound the output to be 0-360. My modular arithmetic skills
    keep crapping out. Maybe pen and paper are the tool for that too.
    This problem of getting a usable figure for angular change is exactly
    what I need to solve to find out how "tight" the bend is in the curve.

    If that all works, then I ought to be able to control ears by snipping
    curves that are all clustered up in a corner.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From luser droog@21:1/5 to luser droog on Thu Aug 8 00:22:51 2019
    On Wednesday, August 7, 2019 at 12:05:51 AM UTC-5, luser droog wrote:
    On Tuesday, July 30, 2019 at 1:18:40 AM UTC-5, luser droog wrote:

    But there may have been errors in my implementation which concealed positive (or promising) results. Thinking over it, using 3 consecutive points should have been pretty close if it were done correctly.

    But I have a new idea which really seems like it should work.
    For lineto segments average the incoming and outgoing vectors as
    described earlier. But for curveto segments, if I do a de Casteljau subdivision just once, then I get a middle point and two tangent
    vectors at that point for cheap. If it doubles the number of curves,
    that's not too terrible I think.

    At long last I've implemented all of these ideas without errors this time. And it still makes ears. But I think this is good progress.

    I found some code in Don Lancaster's pages to measure the length of a curve. I think I can use that to determine if my new offset curve is bigger or smaller than the original. And that should tell me whether we're inside or outside of a bend.

    But a numerical/conceptual problem keeps cropping up. Whenever I try to
    run through a series of line segments to get the angular change, I don't
    know how to bound the output to be 0-360. My modular arithmetic skills
    keep crapping out. Maybe pen and paper are the tool for that too.
    This problem of getting a usable figure for angular change is exactly
    what I need to solve to find out how "tight" the bend is in the curve.

    If that all works, then I ought to be able to control ears by snipping
    curves that are all clustered up in a corner.

    Here's some progress. The image is an inner curve 7 points in. All the
    control points on the original curve are drawn with dots. And it produces
    some text output which shows

    [original line or curve point(s)]
    length
    angle
    (angle out)
    (angle out - angle in)
    [new (inner) line or curve]
    length
    angle
    (angle out)
    (angle change)

    I tweaked the formula for angular change, and it seems ok now.
    Now, I'm just staring at the numbers to see if I can find that
    nasty corner, and hopefully other similar corners more generally.
    Then, to figure out how to snip it nicely...


    Text Output:
    $ gs stroke7.ps
    GPL Ghostscript 9.27 (2019-04-04)
    Copyright (C) 2018 Artifex Software, Inc. All rights reserved.
    This software is supplied under the GNU AGPLv3 and comes with NO WARRANTY:
    see the file COPYING for details.
    Querying operating system for font files...
    Can't find (or can't open) font file /usr/share/ghostscript/9.27/Resource/Font/Calibri-Bold.
    Can't find (or can't open) font file Calibri-Bold.
    Loading Calibri-Bold font from /usr/share/fonts/microsoft/calibrib.ttf... 4678648 3034560 9299916 7891942 1 done.
    [374.530243 302.102081]
    0.0
    270.0
    [366.530243 302.109528]
    0.0
    270.0

    [374.500824 286.917053 373.765564 271.421753 372.236176 255.297028]
    46.8781166
    269.889
    264.581848
    -5.30715942
    [366.500854 286.932556 365.801514 272.179199 364.272125 256.054474]
    46.1354637
    269.889
    264.581848
    -5.30715942

    [370.692078 239.106125 367.800934 223.379929 363.506897 207.921387]
    48.2356453
    264.552277
    254.475891
    -10.0763855
    [362.728027 239.863571 360.09903 225.543411 355.805 210.084869]
    46.80653
    264.552277
    254.475891
    -10.0763855

    [359.092285 192.377533 353.295319 177.766022 345.754272 163.833908]
    47.6239052
    254.144913
    241.574615
    -12.5702972
    [351.390381 194.541016 346.266205 181.585892 338.725159 167.653778]
    45.8383713
    254.144913
    241.574615
    -12.5702972

    [338.11618 149.834152 328.483978 137.737305 316.554779 127.334549]
    46.9916382
    241.383667
    221.089783
    -20.2938843
    [331.087067 153.654022 323.218842 143.760437 311.289642 133.357681]
    44.1787376
    241.383667
    221.089783
    -20.2938843

    [304.599121 116.858261 290.125824 108.681931 273.043762 102.576157]
    50.3612518
    221.226852
    199.668808
    -21.5580444
    [299.333984 122.881401 287.41806 116.209747 270.336 110.103973]
    47.4055
    221.226852
    199.668808
    -21.5580444

    [255.882309 96.3645 235.785599 93.4174881 212.503647 93.4174881]
    61.5353737
    199.897919
    180.0
    -19.8979187
    [253.174545 103.892319 235.769211 101.417473 212.487259 101.417473]
    58.8120461
    199.897919
    180.0
    -19.8979187

    [204.193497 93.3527832 196.140701 93.9910126 188.027603 95.1351089]
    24.5589523
    180.446106
    171.973175
    -8.47293091
    [204.177109 101.352768 197.246796 101.914177 189.133698 103.058273]
    23.4344864
    180.446106
    171.973175
    -8.47293091

    [179.813049 96.2703857 172.522 97.7115326 165.839752 99.4291534]
    22.6118946
    172.131393
    165.584641
    -6.54675293
    [180.919144 104.19355 174.465073 105.471977 167.782822 107.189598]
    21.7595253
    172.131393
    165.584641
    -6.54675293

    [159.048706 101.085007 153.48262 102.958504 148.804764 104.864349]
    17.8974018
    166.297012
    157.833099
    -8.46391296
    [160.991776 108.845451 156.403641 110.406166 151.725784 112.312012]
    16.8712521
    166.297012
    157.833099
    -8.46391296

    [144.048965 106.65844 140.741669 108.730461 138.640228 110.734833]
    11.8126049
    159.331375
    136.354324
    -22.9770508
    [146.969986 114.106102 146.16127 114.615013 144.05983 116.619385]
    8.88110638
    159.331375
    136.354324
    -22.9770508

    [136.460861 112.673035 135.016769 115.647987 134.063843 119.464096]
    10.0036058
    138.351974
    104.020691
    -34.3312836
    [141.880463 118.557587 142.757706 117.667374 141.804779 121.483482]
    5.57857656
    138.351974
    104.020691
    -34.3312836

    [133.049149 123.190498 132.63446 128.622757 132.63446 135.493225]
    16.1340561
    105.232254
    90.0
    -15.232254
    [140.790085 125.209885 140.634293 128.675613 140.634293 135.546082]
    14.155632
    105.232254
    90.0
    -15.232254

    [132.547699 141.754883 132.7771 146.847443 133.063858 150.663559]
    15.1813326
    90.7938385
    85.7026367
    -5.09120178
    [140.547531 141.807739 140.761749 146.352173 141.048508 150.168289]
    14.6331158
    90.7938385
    85.7026367
    -5.09120178

    [133.246216 154.410553 133.922668 157.441376 134.781479 159.539871]
    9.08163
    87.2137451
    67.7431412
    -19.4706039
    [141.230865 153.915283 141.353363 154.477463 142.212173 156.575958]
    6.56146193
    87.2137451
    67.7431412
    -19.4706039

    [135.590286 161.622192 136.688797 163.025101 137.928482 163.692734]
    5.31567192
    68.7730179
    28.3047085
    -40.4683075
    [143.020981 158.658279 140.542328 156.014374 141.782013 156.682]
    2.48282027
    68.7730179
    28.3047085
    -40.4683075

    [139.072571 164.335373 140.646088 164.692719 142.363693 164.692719]
    4.59395552
    29.3230934
    0.0
    -29.3230934
    [142.926102 157.324646 140.431396 156.695602 142.149 156.695602]
    1.69659424
    29.3230934
    0.0
    -29.3230934

    [144.590134 164.586838 147.90184 164.023621 152.098816 162.686874]
    9.96525478
    357.277283
    342.333191
    -14.9440918
    [144.375443 156.589722 145.410614 156.421402 149.60759 155.084656]
    7.65997934
    357.277283
    342.333191
    -14.9440918

    [156.248749 161.288361 161.401596 159.828094 167.410324 158.110489]
    15.9821692
    341.376343
    344.047333
    2.67099
    [153.757523 153.686142 159.174896 152.144226 165.183624 150.42662]
    16.2589684
    341.376343
    344.047333
    2.67099

    [173.413162 156.347275 180.245392 154.864944 187.880554 153.528214]
    20.9876537
    343.63089
    350.06955
    6.43865967
    [171.186462 148.663406 178.85051 146.987488 186.485672 145.650757]
    21.8413944
    343.63089
    350.06955
    6.43865967

    [195.459839 152.170883 204.009689 151.528244 213.362457 151.528244]
    25.5931606
    349.846863
    0.0
    10.1531372
    [194.064957 144.293427 203.974915 143.52832 213.327682 143.52832]
    26.9577065
    349.846863
    0.0
    10.1531372

    [229.376892 151.417953 242.94136 154.389954 254.00882 160.116333]
    41.9528
    359.605408
    27.3573551
    27.7519531
    [229.342117 143.41803 246.596436 147.273743 257.663879 153.000122]
    45.7254677
    359.605408
    27.3573551
    27.7519531

    [265.067444 165.754471 274.049652 173.570511 280.920105 183.304153]
    35.8796844
    27.0143433
    54.7838
    27.769455
    [268.722504 158.63826 280.593048 168.967911 287.463501 178.701553]
    39.6876183
    27.0143433
    54.7838
    27.769455

    [287.717041 193.001053 292.846375 204.294968 296.090424 217.080048]
    37.2313118
    54.9718819
    75.762413
    20.7905312
    [294.260437 188.398453 300.608398 202.358261 303.852448 215.143341]
    40.1534
    54.9718819
    75.762413
    20.7905312

    [299.227142 229.868057 301.150635 243.322235 301.531525 257.444061]
    40.8069153
    76.2182465
    88.4550095
    12.236763
    [306.989166 227.931351 306.847 248.939316 307.227875 263.061157]
    48.1180305
    76.2182465
    88.4550095
    12.236763

    [292.709595 251.882385 282.158325 247.092758 269.755585 242.991364]
    34.993576
    212.228867
    198.298248
    -13.9306183
    [298.405945 257.499481 279.628632 254.682266 267.225891 250.580872]
    42.0174217
    212.228867
    198.298248
    -13.9306183

    [257.235229 238.785553 243.134 236.838516 227.103394 236.838516]
    43.2831955
    198.568115
    180.0
    -18.5681152
    [254.705536 246.375061 243.120193 244.838501 227.089584 244.838501]
    40.741478
    198.568115
    180.0
    -18.5681152

    [207.369919 236.776764 190.748154 239.412 177.004272 244.56192]
    50.9645348
    180.179291
    159.45871
    -20.7205811
    [207.35611 244.776749 193.55 246.905304 179.806122 252.055237]
    48.1140137
    180.179291
    159.45871
    -20.7205811

    [163.194214 249.714767 152.098816 257.157288 143.510742 266.89093]
    40.669857
    159.538315
    131.422226
    -28.1160889
    [165.996063 257.208069 158.081436 262.468445 149.493362 272.202087]
    36.8386078
    159.538315
    131.422226
    -28.1160889

    [134.849121 276.58783 128.671295 288.461151 124.758125 302.39032]
    40.5021286
    131.772385
    105.691833
    -26.0805511
    [140.831741 281.899 136.364914 290.653931 132.451752 304.583099]
    36.965847
    131.772385
    105.691833
    -26.0805511

    [120.752312 316.245972 118.893517 332.067749 118.893517 349.624786]
    47.7526741
    106.125137
    90.0
    -16.1251373
    [128.445938 318.438751 126.893517 332.072968 126.893517 349.63]
    45.5485268
    106.125137
    90.0
    -16.1251373

    [118.869987 368.124481 121.659645 385.165344 127.193375 400.718018]
    52.0193253
    90.072876
    70.4141541
    -19.6587219
    [126.869987 368.1297 129.196487 382.482819 134.730225 398.035492]
    49.3005562
    90.072876
    70.4141541
    -19.6587219

    [132.696213 416.173645 140.931366 429.730743 151.810593 441.087921]
    47.6400299
    70.4021835
    46.2313309
    -24.1708527
    [140.233063 413.491119 146.718033 424.206757 157.59726 435.563934]
    44.3188629
    70.4021835
    46.2313309
    -24.1708527

    [162.610397 452.440643 176.236633 461.269897 192.45694 467.563904]
    48.9071388
    46.4297829
    21.2078724
    -25.2219105
    [168.397064 446.916656 179.128 453.810669 195.348312 460.104675]
    45.4430084
    46.4297829
    21.2078724
    -25.2219105

    [208.605194 473.816742 227.481339 477.010803 248.861847 477.010803]
    57.5152283
    21.1671257
    0.0
    -21.1671257
    [211.496567 466.357513 227.469421 469.010803 248.84993 469.010803]
    54.5728455
    21.1671257
    0.0
    -21.1671257

    [265.980652 476.953461 281.061279 475.006439 293.94342 470.999146]
    45.6468887
    359.808075
    342.720459
    -17.087616
    [265.96875 468.953461 278.666473 467.373291 291.548615 463.366]
    43.241848
    359.808075
    342.720459
    -17.087616

    [306.803467 466.930115 317.893 461.265503 327.148712 453.822968]
    37.6011848
    342.442169
    321.197144
    -21.2450256
    [304.408661 459.296967 312.850464 455.05481 322.106171 447.612274]
    34.6070518
    342.442169
    321.197144
    -21.2450256

    [336.360321 446.271637 344.136658 437.361511 350.336548 426.770508]
    35.8204536
    320.656403
    300.34436
    -20.3120422
    [331.31778 440.060944 337.216156 433.348175 343.416046 422.757172]
    32.9398384
    320.656403
    300.34436
    -20.3120422

    [356.418793 416.182465 361.406952 404.397369 364.936279 391.418182]
    38.3531647
    299.874969
    285.212158
    -14.6628113
    [349.498291 412.169128 353.680267 402.324127 357.209595 389.34494]
    36.2558594
    299.874969
    285.212158
    -14.6628113

    [368.374481 378.430176 370.950897 364.414246 372.383209 349.336578]
    42.783493
    284.827301
    275.426575
    -9.40072632
    [360.647797 376.356934 362.985 363.676361 364.417297 348.598694]
    41.4277954
    284.827301
    275.426575
    -9.40072632

    [373.747894 334.219177 374.530243 318.51358 374.530243 302.102081]
    47.2993202
    275.158264
    270.0
    -5.15826416
    [365.781982 333.481293 366.530243 318.51358 366.530243 302.102081]
    46.5608063
    275.158264
    270.0
    -5.15826416

    [299.813904 312.407806]
    0.0
    35.9335518
    [307.49585 310.1745]
    0.0
    35.9335518

    [299.716827 332.80304 298.66687 349.910095 296.372772 363.648071]
    51.4078865
    90.2727127
    99.4803162
    9.20760345
    [307.716736 332.841125 306.553772 351.250519 304.259674 364.988495]
    52.7141571
    90.2727127
    99.4803162
    9.20760345

    [294.003693 377.347839 290.646423 388.362366 286.067078 396.571045]
    34.6546516
    99.8110428
    119.155678
    19.344635
    [301.890594 378.688263 297.617279 392.287567 293.037933 400.496246]
    37.3885384
    99.8110428
    119.155678
    19.344635

    [281.458344 404.679718 275.767242 410.600189 268.89679 414.035431]
    24.8555794
    119.61264
    153.434845
    33.8222046
    [288.429199 408.604919 279.319 417.768524 272.448547 421.203766]
    29.5303707
    119.61264
    153.434845
    33.8222046

    [261.945435 417.448608 254.010284 419.188293 244.850143 419.188293]
    24.8113022
    153.848541
    180.0
    26.1514587
    [265.497192 424.616943 253.962524 427.188141 244.802383 427.188141]
    28.4879551
    153.848541
    180.0
    26.1514587

    [235.781189 419.079468 228.147491 417.613312 221.662323 414.464844]
    23.8575554
    180.6875
    205.896
    25.2084961
    [235.733429 427.079315 224.64093 424.803864 218.155762 421.655396]
    27.3933258
    180.6875
    205.896
    25.2084961

    [215.15361 411.276672 209.786057 406.878204 205.492 401.15332]
    21.1453419
    206.097061
    233.127609
    27.0305481
    [211.647049 418.467224 203.369659 411.65625 199.075607 405.931366]
    24.9063759
    206.097061
    233.127609
    27.0305481

    [201.170029 395.307831 197.999496 388.650574 195.898056 380.824249]
    22.6140194
    233.521912
    254.970062
    21.4481506
    [194.753632 400.085876 190.280334 390.751678 188.178894 382.925354]
    25.5813694
    233.521912
    254.970062
    21.4481506

    [193.706924 372.88028 192.751053 364.412781 192.751053 355.06]
    26.0351067
    254.57991
    270.0
    15.4200897
    [185.987762 374.981384 184.751083 364.432068 184.751083 355.079285]
    28.1320286
    254.57991
    270.0
    15.4200897

    [192.704 344.880768 193.656921 336.025024 195.468658 328.295746]
    26.9633083
    269.735138
    283.191925
    13.4567871
    [184.704025 344.900055 185.858459 334.240692 187.670197 326.511414]
    28.7757702
    269.735138
    283.191925
    13.4567871

    [197.205383 320.516479 200.142105 314.178345 204.056747 309.119629]
    21.1823483
    282.58493
    307.734192
    25.1492615
    [189.406921 318.732147 193.784897 309.321808 197.699539 304.263092]
    24.5610504
    282.58493
    307.734192
    25.1492615

    [207.918442 303.999115 213.074234 300.290344 219.36824 297.808044]
    19.2845936
    307.022186
    338.476166
    31.4539795
    [201.561234 299.142578 210.109406 292.860016 216.403412 290.377716]
    23.5147915
    307.022186
    338.476166
    31.4539795

    [225.656372 295.269867 233.300354 294.084595 242.273727 294.084595]
    23.3466854
    338.018738
    0.0
    21.9812622
    [222.691544 287.839539 233.272598 286.084656 242.245972 286.084656]
    26.3277283
    338.018738
    0.0
    21.9812622

    [253.223526 294.015472 263.786591 295.755157 273.614349 299.096252]
    31.8869267
    359.638306
    18.7762413
    19.1379395
    [253.19577 286.015533 266.326111 288.168945 276.15387 291.51004]
    34.4805
    359.638306
    18.7762413
    19.1379395

    [283.423 302.328552 292.178741 306.874054 299.813904 312.407806]
    29.5047092
    18.2388859
    35.9335518
    17.6946659
    [285.962524 294.74234 296.873505 300.396484 304.508667 305.930237]
    31.9229279
    18.2388859
    35.9335518
    17.6946659

    showpage, press <return> to continue<<


    Code:

    $ cat stroke7.ps
    %!

    %errordict/rangecheck{pstack countexecstack array execstack == quit}put %errordict/typecheck{pstack countexecstack array execstack == quit}put %errordict/rangecheck{pstack countexecstack array execstack == quit}put

    /args-begin { dup length dict begin { exch def } forall } def
    /map { [ 3 1 roll forall ] } def
    /spill { aload pop } def
    /head { 0 exch getinterval } def
    /tail { 1 index length 1 index sub getinterval } def
    /last { 1 index length 1 index sub exch getinterval } def
    /droplast { 1 index length exch sub 0 exch getinterval } def
    /fortuplem { {p m n a} args-begin
    0 n /a load length m sub
    [ /a load /exch cvx m /getinterval cvx /p load /exec cvx ] cvx
    end for } def
    /p-p { exch 3 1 roll sub 3 1 roll sub exch } def
    /p+v { exch 3 1 roll add 3 1 roll add exch } def
    /v*n { 3 2 roll 1 index mul 3 1 roll mul } def
    /perp { exch neg } def
    /mag { dup mul exch dup mul exch add sqrt } def
    /norm { 2 copy mag dup 0 eq { }{ 1 exch div } ifelse v*n } def
    /ang { exch atan } def
    /aa { array astore } def
    /cat { 2 aa {spill} map } def
    /median { p+v .5 v*n } def
    /closedsubpaths { [ {[ 3 1 roll 2 aa} {2 aa} {6 aa} {]} pathforall ] {{]}stopped{exit}if}loop } def
    /drawpath {
    {
    dup length 1 eq { spill spill moveto }{
    dup 1 head spill spill moveto
    1 tail { dup length 2 eq { spill lineto }{ spill curveto } ifelse } forall
    closepath
    } ifelse
    } forall
    } def


    /offsetbyvectors {
    { dup length 1 ne {offsetsubpath} if } map
    } def

    /pq { pstack / = quit } def

    /offsetsubpath {
    [ exch
    dup dup 2 last exch 2 head cat offsetelement
    1 index dup 1 last exch 3 head cat offsetelement
    2 index 1 4 { offsetelement } fortuplem
    counttomark -1 roll
    dup 3 last exch 1 head cat offsetelement
    ]
    } def

    /offsetelement {
    dup 2 get length 2 eq { offsetline }{ offsetcurve } ifelse
    } def

    /offsetline {
    dup def_vin dup def_vout
    dup 1 tail 2 head spill exch 2 last exch printelement
    dup 2 get spill
    vin vout p+v norm perp n v*n p+v
    %vin norm perp n v*n p+v
    %vout norm perp n v*n p+v
    %vin mag 0 ne vout mag 0 ne and {vin ang dup =only ( )print vout ang dup =only ( )print exch sub =} if
    2 aa
    exch 1 get 2 last fixline 1 index printelement / =
    } def

    /fixline {
    spill vin vout p+v norm perp n v*n p+v 2 aa
    } def

    /offsetcurve {
    dup def_vin dup def_vout dup def_venter dup def_vexit
    dup 1 tail 2 head spill exch 2 last exch printelement
    dup 2 get spill
    6 4 roll
    %vin norm perp n v*n p+v
    vin venter p+v norm perp n v*n p+v
    6 4 roll
    %vout norm perp n v*n p+v
    vout vexit p+v norm perp n v*n p+v
    6 4 roll
    %vout norm perp n v*n p+v
    vout vexit p+v norm perp n v*n p+v
    6 aa
    exch 1 get 2 last fixcurve 1 index printelement / =
    } def

    /fixcurve {
    spill vin venter p+v norm perp n v*n p+v 2 aa
    } def

    /def_vin {
    dup 1 get 2 last spill 2 index 2 get 2 head spill 4 2 roll p-p
    2 copy mag .05 lt {
    pop pop dup 1 get length 2 eq {
    dup 0 get 2 last spill 2 index 1 get spill 4 2 roll p-p
    }{ dup 1 get 4 last spill 4 2 roll p-p } ifelse
    } if
    2 aa exch pop cvx /vin exch def
    } def

    /def_vout {
    dup 2 get 2 last spill 2 index 3 get 2 head spill 4 2 roll p-p
    2 aa exch pop cvx /vout exch def
    } def

    /def_venter { % quad
    dup 1 get dup length 6 eq { % q q1
    %dup ==
    4 last spill % q x1 y1 x2 y2
    4 2 roll p-p % q dx dy
    3 2 roll pop
    }{ % q q1
    spill % q x1 y1
    3 2 roll % x1 y1 q
    0 get 2 last % x1 y1 q0{..2}
    spill % x1 y1 x2 y2
    p-p
    } ifelse
    2 aa cvx /venter exch def
    %/venter load == / =
    } def

    /def_vexit {
    2 get 4 last spill 4 2 roll p-p
    2 aa cvx /vexit exch def
    } def

    /offsetpath {
    /n exch def
    closedsubpaths
    %dup printlengths
    %dup drawpoints
    offsetbyvectors %dup ==
    newpath
    drawpath
    } def

    /drawpathpoints {
    closedsubpaths drawpoints
    } def

    /circ {
    gsave
    0 setgray
    newpath 1 0 360 arc stroke
    grestore
    } def

    /drawpoints {
    { { 2 2 { spill circ } fortuplem } forall } forall
    } def



    /xtt {
    x3 x2 3 mul sub
    x1 3 mul add x0 sub tt 3 exp mul
    x2 3 mul x1 6 mul neg add
    x0 3 mul add tt dup mul mul add
    x1 3 mul x0 3 mul neg add tt mul add x0 add
    } def % x coefficients as f(t)

    /ytt {
    y3 y2 3 mul sub
    y1 3 mul add y0 sub tt 3 exp mul
    y2 3 mul y1 6 mul neg add
    y0 3 mul add tt dup mul mul add
    y1 3 mul y0 3 mul neg add tt mul add y0 add
    } def % y coefficients as f(t)

    /bezierlength {
    /oldx x0 def /oldy y0 def /blength 0 def
    0 1 numpoints 1 sub div 1.0001 {
    /tt exch def
    xtt ytt /newy exch def /newx exch def
    newx oldx sub dup mul
    newy oldy sub dup mul add sqrt
    blength add /blength exch def
    /oldy newy def /oldx newx def
    } for
    }def % approximate curve with line segments

    /numpoints 37 def

    /printelement {
    dup ==
    2 copy printelementlength
    printelementangles
    } def

    /printelementangles {
    dup length 2 eq {
    vin ang ==
    }{
    vin ang == vexit ang ==
    vexit ang vin ang a-a ==
    } ifelse
    pop pop
    } def

    /a-a {
    2 copy 270 gt exch 90 lt and { exch 360 add exch } if
    sub
    } def

    /printlengths {
    { printsubpathlengths } forall
    } def

    /printsubpathlengths {
    dup 1 last spill 2 last 1 index 0 get printelementlength
    1 2 { spill exch 2 last printelementlength }fortuplem
    } def

    /printelementlength {
    dup length 2 eq { printlinelength }{ printcurvelength } ifelse
    } def

    /printlinelength {
    exch spill 3 2 roll
    spill 4 2 roll p-p mag ==
    } def

    /printcurvelength {
    exch spill 3 2 roll
    spill {y3 x3 y2 x2 y1 x1 y0 x0}{exch def}forall
    bezierlength blength ==
    } def


    3 3 scale
    /Calibri-Bold 600 selectfont
    100 100 moveto (9) true charpath
    gsave stroke grestore
    gsave drawpathpoints grestore
    %gsave 20 setlinewidth strokepath 1 setlinewidth stroke grestore

    %flattenpath
    1 0 0 setrgbcolor
    gsave 8 offsetpath stroke grestore
    %gsave 7 offsetpath 3 offsetpath stroke grestore
    %gsave 7 offsetpath 3 offsetpath 3 offsetpath stroke grestore
    %gsave 10 offsetpath stroke grestore
    %gsave -20 offsetpath stroke grestore

    showpage quit

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From luser droog@21:1/5 to luser droog on Sun Aug 11 13:33:37 2019
    On Thursday, August 8, 2019 at 2:22:52 AM UTC-5, luser droog wrote:
    On Wednesday, August 7, 2019 at 12:05:51 AM UTC-5, luser droog wrote:
    On Tuesday, July 30, 2019 at 1:18:40 AM UTC-5, luser droog wrote:

    But there may have been errors in my implementation which concealed positive (or promising) results. Thinking over it, using 3 consecutive points should have been pretty close if it were done correctly.

    But I have a new idea which really seems like it should work.
    For lineto segments average the incoming and outgoing vectors as described earlier. But for curveto segments, if I do a de Casteljau subdivision just once, then I get a middle point and two tangent
    vectors at that point for cheap. If it doubles the number of curves, that's not too terrible I think.

    At long last I've implemented all of these ideas without errors this time. And it still makes ears. But I think this is good progress.

    I found some code in Don Lancaster's pages to measure the length of a curve.
    I think I can use that to determine if my new offset curve is bigger or smaller than the original. And that should tell me whether we're inside or outside of a bend.

    But a numerical/conceptual problem keeps cropping up. Whenever I try to
    run through a series of line segments to get the angular change, I don't know how to bound the output to be 0-360. My modular arithmetic skills
    keep crapping out. Maybe pen and paper are the tool for that too.
    This problem of getting a usable figure for angular change is exactly
    what I need to solve to find out how "tight" the bend is in the curve.

    If that all works, then I ought to be able to control ears by snipping curves that are all clustered up in a corner.

    Here's some progress. The image is an inner curve 7 points in. All the control points on the original curve are drawn with dots. And it produces some text output which shows

    [original line or curve point(s)]
    length
    angle
    (angle out)
    (angle out - angle in)
    [new (inner) line or curve]
    length
    angle
    (angle out)
    (angle change)

    I tweaked the formula for angular change, and it seems ok now.
    Now, I'm just staring at the numbers to see if I can find that
    nasty corner, and hopefully other similar corners more generally.
    Then, to figure out how to snip it nicely...


    Text Output:
    $ gs stroke7.ps
    [snip]
    [133.049149 123.190498 132.63446 128.622757 132.63446 135.493225]
    16.1340561
    105.232254
    90.0
    -15.232254
    [140.790085 125.209885 140.634293 128.675613 140.634293 135.546082]
    14.155632
    105.232254
    90.0
    -15.232254

    [132.547699 141.754883 132.7771 146.847443 133.063858 150.663559]
    15.1813326
    90.7938385
    85.7026367
    -5.09120178
    [140.547531 141.807739 140.761749 146.352173 141.048508 150.168289] 14.6331158
    90.7938385
    85.7026367
    -5.09120178

    [133.246216 154.410553 133.922668 157.441376 134.781479 159.539871]
    9.08163
    87.2137451
    67.7431412
    -19.4706039
    [141.230865 153.915283 141.353363 154.477463 142.212173 156.575958] 6.56146193
    87.2137451
    67.7431412
    -19.4706039

    [135.590286 161.622192 136.688797 163.025101 137.928482 163.692734] 5.31567192
    68.7730179
    28.3047085
    -40.4683075
    [143.020981 158.658279 140.542328 156.014374 141.782013 156.682]
    2.48282027
    68.7730179
    28.3047085
    -40.4683075

    [139.072571 164.335373 140.646088 164.692719 142.363693 164.692719] 4.59395552
    29.3230934
    0.0
    -29.3230934
    [142.926102 157.324646 140.431396 156.695602 142.149 156.695602]
    1.69659424
    29.3230934
    0.0
    -29.3230934

    [144.590134 164.586838 147.90184 164.023621 152.098816 162.686874]
    9.96525478
    357.277283
    342.333191
    -14.9440918
    [144.375443 156.589722 145.410614 156.421402 149.60759 155.084656]
    7.65997934
    357.277283
    342.333191
    -14.9440918

    [snip]

    Using this output was enough to isolate that bad corner. This is the
    bottom left corner of the '9' where it turns from about 90 degrees
    (North) to about 342 degrees (ENE). I translated and scaled and got
    some smaller numbers, then copied them 3 places in my notebook and
    plotted the points by hand. And then, I found something.

    The shapes of the control polygons of the curves in the input curve
    are all disjoint and "normal". But each of these maps to a strange
    shape and several of them overlap each other. One of them is "twisted"
    where the incoming tangent vector intersects the outgoing one. Two of
    them look like a "thorny triangle", where the vector from the first
    control point to the second is almost perpendicular to the vector from
    the start point the final point. And one of them is "inverted", where
    the vector from the first control point to the second is much longer
    than the vector from the start point to the final point.

    So, the new strategy is: run through the original curve and the new
    generated curve and discover where a "normal, disjoint" curve maps
    to a "strange, overlapping" curve, and replace sequences of these
    with a new curve built from the incoming tangent vector of the first
    curve and the outgoing tangent of the last curve. Checking for overlap
    is going to be a little painful, so hopefully I can get away with
    just checking for strangeness. This involves computing an intersection
    for the "twisted" case, but for the other cases it's simple vector
    operations.

    I'm thinking about using a PostScript trick to keep all this data
    organized. I have a path array of subpath arrays of element arrays of coordinates. I can make associations between the element arrays and
    other useful data by making definitions in a dictionary, using the
    element arrays as keys. Then I can use simple 'forall' loops and load associated data back out of the dict, having the element to hand.

    This should help clean up a lot of the crazy loops, I think. I just
    need one crazy loop to collect all of the contextual vectors, then
    just 'forall' loops after that.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From luser droog@21:1/5 to luser droog on Tue Aug 13 16:03:57 2019
    On Sunday, August 11, 2019 at 3:33:38 PM UTC-5, luser droog wrote:
    On Thursday, August 8, 2019 at 2:22:52 AM UTC-5, luser droog wrote:
    On Wednesday, August 7, 2019 at 12:05:51 AM UTC-5, luser droog wrote:

    At long last I've implemented all of these ideas without errors this time.
    And it still makes ears. But I think this is good progress.

    I found some code in Don Lancaster's pages to measure the length of a curve.
    I think I can use that to determine if my new offset curve is bigger or smaller than the original. And that should tell me whether we're inside or
    outside of a bend.

    But a numerical/conceptual problem keeps cropping up. Whenever I try to run through a series of line segments to get the angular change, I don't know how to bound the output to be 0-360. My modular arithmetic skills keep crapping out. Maybe pen and paper are the tool for that too.
    This problem of getting a usable figure for angular change is exactly what I need to solve to find out how "tight" the bend is in the curve.

    [snip]

    Using this output was enough to isolate that bad corner. This is the
    bottom left corner of the '9' where it turns from about 90 degrees
    (North) to about 342 degrees (ENE). I translated and scaled and got
    some smaller numbers, then copied them 3 places in my notebook and
    plotted the points by hand. And then, I found something.

    The shapes of the control polygons of the curves in the input curve
    are all disjoint and "normal". But each of these maps to a strange
    shape and several of them overlap each other. One of them is "twisted"
    where the incoming tangent vector intersects the outgoing one. Two of
    them look like a "thorny triangle", where the vector from the first
    control point to the second is almost perpendicular to the vector from
    the start point the final point. And one of them is "inverted", where
    the vector from the first control point to the second is much longer
    than the vector from the start point to the final point.

    So, the new strategy is: run through the original curve and the new
    generated curve and discover where a "normal, disjoint" curve maps
    to a "strange, overlapping" curve, and replace sequences of these
    with a new curve built from the incoming tangent vector of the first
    curve and the outgoing tangent of the last curve. Checking for overlap
    is going to be a little painful, so hopefully I can get away with
    just checking for strangeness. This involves computing an intersection
    for the "twisted" case, but for the other cases it's simple vector operations.

    I'm thinking about using a PostScript trick to keep all this data
    organized. I have a path array of subpath arrays of element arrays of coordinates. I can make associations between the element arrays and
    other useful data by making definitions in a dictionary, using the
    element arrays as keys. Then I can use simple 'forall' loops and load associated data back out of the dict, having the element to hand.

    This should help clean up a lot of the crazy loops, I think. I just
    need one crazy loop to collect all of the contextual vectors, then
    just 'forall' loops after that.

    Mostly there. Everything works. Ear detection seems to be working just
    by checking two vectors from each curve, the "control vector" that goes
    from the first control point to the second, and the "transit vector"
    that goes straight from the first point to the final point. If the
    control vector is bigger than the transit vector, that's an "inverted
    curve" by my definition. And if the angular difference between the
    control vector and the transit vector is more than about 80 degrees,
    then that's either a thorny triangle or a twisted curve if it's even
    bigger, closer to 180 degrees.

    When drawing an offset curve 10 points away, I get 2 ears which matches
    the visual results. At 5 points away, I get this "ear report":

    [(00000000000000011000000000000000000000000) (000000000000000)]

    which matches the visual results. At only 2 points away, no ears are
    found, which also looks right.

    So, now for the chore of actually snipping the ears. I think the same PostScript trick of using a dict as an associative array will help.
    I can associate each string of the ear report with the subpath array
    it belongs to. Then I can use a plain 'forall' loop instead of a weird
    'for' loop to access parallel arrays.

    I expect to have some pretty output soon. Wish me luck!

    droog

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From luser droog@21:1/5 to luser droog on Mon Aug 19 22:46:23 2019
    On Tuesday, August 13, 2019 at 6:03:58 PM UTC-5, luser droog wrote:

    Mostly there. Everything works. Ear detection seems to be working just
    by checking two vectors from each curve, the "control vector" that goes
    from the first control point to the second, and the "transit vector"
    that goes straight from the first point to the final point. If the
    control vector is bigger than the transit vector, that's an "inverted
    curve" by my definition. And if the angular difference between the
    control vector and the transit vector is more than about 80 degrees,
    then that's either a thorny triangle or a twisted curve if it's even
    bigger, closer to 180 degrees.

    When drawing an offset curve 10 points away, I get 2 ears which matches
    the visual results. At 5 points away, I get this "ear report":

    [(00000000000000011000000000000000000000000) (000000000000000)]

    which matches the visual results. At only 2 points away, no ears are
    found, which also looks right.

    So, now for the chore of actually snipping the ears. I think the same PostScript trick of using a dict as an associative array will help.
    I can associate each string of the ear report with the subpath array
    it belongs to. Then I can use a plain 'forall' loop instead of a weird
    'for' loop to access parallel arrays.

    I expect to have some pretty output soon. Wish me luck!


    Sigh. Same story again. It all works, but it doesn't "work". I tried
    snipping the ears by replacing the subsequence of curves with a single
    curve. And I tried grabbing earlier and/or later curves as well.

    I think it might work if I use 2 curves. I have a sketch of how to
    do it, but nothing typed in yet.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From luser droog@21:1/5 to luser droog on Wed Aug 21 23:44:46 2019
    On Tuesday, August 20, 2019 at 12:46:24 AM UTC-5, luser droog wrote:
    On Tuesday, August 13, 2019 at 6:03:58 PM UTC-5, luser droog wrote:

    So, now for the chore of actually snipping the ears. I think the same PostScript trick of using a dict as an associative array will help.
    I can associate each string of the ear report with the subpath array
    it belongs to. Then I can use a plain 'forall' loop instead of a weird 'for' loop to access parallel arrays.

    I expect to have some pretty output soon. Wish me luck!


    Sigh. Same story again. It all works, but it doesn't "work". I tried
    snipping the ears by replacing the subsequence of curves with a single
    curve. And I tried grabbing earlier and/or later curves as well.

    I think it might work if I use 2 curves. I have a sketch of how to
    do it, but nothing typed in yet.

    With two curves, the output still isn't right. But it is interesting.
    The whole thing sorta halfway works. I can snip the ears, but I still
    need to devise a suitable replacement fragment.

    On the other hand, checking for self-intersection seems like a more
    correct method. Then I'd have a point of intersection and I could
    just chop it at that point.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From jdaw1@21:1/5 to All on Thu Oct 10 15:07:58 2019
    This is an important and difficult problem. I want this to work, but I want this to work when applied to complicated path returned by charpath, and to work with total reliability. Which is a big ask.

    E.g., a different route to this answer was requested in 2005: https://groups.google.com/forum/#!msg/comp.lang.postscript/VgN-zQSklVI/ (“What is wanted is to do the partial stroke of this path that is ≤6pt from the path, but >5pt.”)

    Currently the equivalent is achieved by the ugly-tastic likes of 1 setlinejoin 1 setlinecap gsave 0 setgray 12 setlinewidth stroke grestore 1 setgray 10 setlinewidth stroke.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From jdaw1@21:1/5 to All on Thu Oct 10 15:09:51 2019
    Better link: https://groups.google.com/forum/#!topic/comp.lang.postscript/VgN-zQSklVI

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From luser droog@21:1/5 to luser droog on Sun Oct 13 01:51:56 2019
    On Sunday, October 13, 2019 at 3:39:46 AM UTC-5, luser droog wrote:

    Now that you mention it, I pulled up the code I have and took a look at
    where I had left off. Just like all the other versions, it *almost*
    works. That is the code all functions correctly but it still *doesn't
    quite look right*. It looks like my last several messages were all talk,
    so here's what I've got. The descriptive comments were added just now
    because I didn't bother with anything like that while writing it.

    %!
    %errordict/typecheck{countexecstack array execstack == quit}put
    [...]
    showpage quit

    Since I copied straight out of emacs instead of xterm, it didn't show the filename. FWIW this one is 'stroke9.ps'.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From luser droog@21:1/5 to All on Sun Oct 13 01:39:44 2019
    On Thursday, October 10, 2019 at 5:07:59 PM UTC-5, jdaw1 wrote:
    This is an important and difficult problem. I want this to work, but I want this to work when applied to complicated path returned by charpath, and to work with total reliability. Which is a big ask.

    E.g., a different route to this answer was requested in 2005: https://groups.google.com/forum/#!msg/comp.lang.postscript/VgN-zQSklVI/ (“What is wanted is to do the partial stroke of this path that is ≤6pt from the path, but >5pt.”)

    Currently the equivalent is achieved by the ugly-tastic likes of 1 setlinejoin 1 setlinecap gsave 0 setgray 12 setlinewidth stroke grestore 1 setgray 10 setlinewidth stroke.

    Now that you mention it, I pulled up the code I have and took a look at
    where I had left off. Just like all the other versions, it *almost*
    works. That is the code all functions correctly but it still *doesn't
    quite look right*. It looks like my last several messages were all talk,
    so here's what I've got. The descriptive comments were added just now
    because I didn't bother with anything like that while writing it.

    %!
    %errordict/typecheck{countexecstack array execstack == quit}put
    /args-begin { dup length dict begin { exch def } forall } def
    /map { [ 3 1 roll forall ] } def
    /spill { aload pop } def
    /head { 0 exch getinterval } def
    /tail { 1 index length 1 index sub getinterval } def
    /last { 1 index length 1 index sub exch getinterval } def
    /droplast { 1 index length exch sub 0 exch getinterval } def
    /fortuplem { {p m n a} args-begin
    0 n /a load length m sub
    [ /a load /exch cvx m /getinterval cvx /p load /exec cvx ] cvx
    end for } def
    /p-p { exch 3 1 roll sub 3 1 roll sub exch } def
    /p+v { exch 3 1 roll add 3 1 roll add exch } def
    /v*n { 3 2 roll 1 index mul 3 1 roll mul } def
    /a-a { 2 copy 270 gt exch 90 lt and { exch 360 add exch } if sub } def
    /perp { exch neg } def
    /mag { dup mul exch dup mul exch add sqrt } def
    /norm { 2 copy mag dup 0 eq { }{ 1 exch div } ifelse v*n } def
    /ang { exch atan } def
    /aa { array astore } def
    /cat { 2 aa {spill} map } def
    /median { p+v .5 v*n } def
    /max { 2 copy lt { exch } if pop } def
    /min { 2 copy gt { exch } if pop } def
    /sign { dup 0 ne { 0 gt { 1 }{ -1 } ifelse } if } def
    /ps {pstack / =} def /pq {ps quit} def

    % The path data structure is an array of subpaths.
    % A subpath is an array of segments.
    % A segment is a length 2 array for a moveto (if first in the subpath) or lineto (general position)
    % or a length 6 array for a curveto segment.
    % For this application (operating upon the result of charpath), subpaths are assumed to be closed,
    % with the last point coincident with the first. Except the very last subpath which will be a single
    % moveto which positions the current point for the next glyph.
    /closedsubpaths { [ {[ 3 1 roll 2 aa} {2 aa} {6 aa} {]} pathforall ] {{]}stopped{exit}if}loop } def
    /drawpath {
    { dup length 1 eq { spill spill moveto }{
    dup 1 head spill spill moveto
    1 tail { dup length 2 eq { spill lineto }{ spill curveto } ifelse } forall
    closepath
    } ifelse
    } forall
    } def


    % The intersection algorithm here has been copied from online refs,
    % but also modified to consider coincident points not be an intersection.
    % Or at least I tried. Consequently these are no longer in the
    % general form. Although they also don't quite succeed in letting
    % coincident points to pass. This leads to extra logic in the double-loop
    % in find-intersections not to try comparing segments that are within
    % a distance of two from each other.
    /orientation {
    {p3y p3x p2y p2x p1y p1x} args-begin
    p2y p1y sub p3x p2x sub mul
    p2x p1x sub p3y p2y sub mul sub
    sign
    end
    } def
    /colinear { orientation 0 eq } def
    /cw { orientation 0 gt } def
    /ccw { orientation 0 lt } def
    %0 0 4 4 1 2 orientation = 0 0 4 4 1 2 ccw = 0 0 4 4 1 1 orientation =

    /onsegment {
    {ry rx qy qx py px} args-begin
    rx qx eq ry qy eq and px qx eq py qy eq and or {
    false
    }{
    qx px rx max le qx px rx min ge and
    qy py ry max le and qy py ry min ge and
    } ifelse
    end
    } def

    /intersect? {
    {q2y q2x p2y p2x q1y q1x p1y p1x} args-begin
    /o1 p1x p1y q1x q1y p2x p2y orientation def
    /o2 p1x p1y q1x q1y q2x q2y orientation def
    /o3 p2x p2y q2x q2y p1x p1y orientation def
    /o4 p2x p2y q2x q2y q1x q1y orientation def
    o1 o2 ne o3 o4 ne and { true }{
    o1 0 eq p1x p1y p2x p2y q1x q1y onsegment and { true }{
    o2 0 eq p1x p1y q2x q2y q1x q1y onsegment and { true }{
    o3 0 eq p2x p2y p1x p1y q2x q2y onsegment and { true }{
    o4 0 eq p2x p2y q1x q1y q2x q2y onsegment and { true }{
    false
    } ifelse} ifelse} ifelse} ifelse} ifelse
    end
    } def
    %1 1 10 1 1 2 10 2 intersect? = 10 0 0 10 0 0 10 10 intersect? = -5 -5 0 0 1 1 10 10 intersect? =


    % The primary workhorse function.
    % After capturing the current path as an array, we first create a dict called 'context'.
    % The context keys are the segments in the path, the values are an array holding an array
    % of points which includes all the points in the segment as well as extra surrounding points.
    %
    % Next we use all these points to populate a dict called 'vectors', the keys of which are
    % segments, and the values are vectors between adjacent points in the context, as well as
    % the 'transit vector' which goes straight from the start point to the endpoint of a curve.
    %
    % Next, we use the context and the vectors to produce the offset of each point along the normal.
    % This process yields the new path on the stack, but we all populate a dict called 'parents'.
    % The parents keys are the segments of the new path, the values are the corresponding segments
    % in the original path.
    %
    % Next, we run through the new path twice to populate context and vectors with new association.
    %
    % Then, use all the information gathered about the path to find any ears and snip them.
    %
    % Finally, install the path back into the graphics state.
    /offsetpath {
    /n exch def
    %flattenpath
    closedsubpaths
    /context 1 dict def
    gathercontext
    /vectors 1 dict def
    computevectors
    /parents 1 dict def
    computeoffset
    gathercontext
    computevectors
    snipears
    newpath
    drawpath
    } def


    % collect 2 earlier and 1 later points for all elements
    /gathercontext {
    dup
    { dup length 1 ne {subpathcontext}{pop} ifelse } forall
    } def
    /subpathcontext {
    dup dup 2 last exch 2 head cat elementcontext
    dup dup 1 last exch 3 head cat elementcontext
    dup 1 4 { elementcontext } fortuplem
    dup 3 last exch 1 head cat elementcontext
    } def
    /elementcontext {dup 2 get length 2 eq { linecontext }{ curvecontext } ifelse} def
    /linecontext {
    %dup == / =
    dup 2 get spill 2 index 1 get 2 last spill p-p
    mag .05 gt { dup 1 get 2 last spill }{
    dup 1 get length 2 gt { dup 1 get 4 last 2 head spill }{
    dup 0 get 2 last spill
    } ifelse
    } ifelse % c0
    2 index 2 get spill % c1
    4 index 3 get 2 head spill % c2
    6 aa exch 2 get exch context 3 1 roll put
    } def
    /curvecontext {
    %dup == / =
    dup 1 get length 2 eq {
    dup 0 get 2 last spill % c0
    2 index 1 get spill % c1
    }{
    dup 1 get 4 last spill % c0 c1
    } ifelse
    4 index 2 get spill % c2 c3 c4
    10 index 3 get 2 head spill % c5
    12 aa exch 2 get exch context 3 1 roll put
    } def


    % calc vectors between context points
    /computevectors {
    dup { dup length 1 ne {subpathvectors}{pop} ifelse } forall
    } def
    /subpathvectors { { elementvectors } forall } def
    /elementvectors { dup length 2 eq { linevectors }{ curvevectors } ifelse } def /linevectors {
    context 1 index get
    dup 4 head spill 4 2 roll p-p
    3 2 roll 4 last spill 4 2 roll p-p
    4 aa vectors 3 1 roll put
    } def
    /curvevectors {
    context 1 index get
    dup 0 4 getinterval spill 4 2 roll p-p % v01
    2 index 2 4 getinterval spill 4 2 roll p-p % v12
    4 index 4 4 getinterval spill 4 2 roll p-p % v23
    6 index 6 4 getinterval spill 4 2 roll p-p % v34
    8 index 8 4 getinterval spill 4 2 roll p-p % v45
    10 index 8 2 getinterval spill
    12 index 2 2 getinterval spill p-p % v14 the 'transit vector'
    12 aa exch pop vectors 3 1 roll put
    } def


    % use vectors to calc normals and offset elements' points
    /computeoffset { { dup length 1 ne { subpathoffset } if } map } def /subpathoffset { { elementoffset } map } def
    /elementoffset { dup length 2 eq { lineoffset }{ curveoffset } ifelse } def /lineoffset {
    vectors 1 index get spill p+v norm perp n v*n
    2 index spill p+v 2 aa
    dup 3 2 roll parents 3 1 roll put
    } def
    /curveoffset {
    vectors 1 index get
    1 index 0 2 getinterval spill 2 index 0 4 getinterval spill p+v norm perp n v*n p+v
    3 index 2 2 getinterval spill 4 index 6 4 getinterval spill p+v norm perp n v*n p+v
    5 index 4 2 getinterval spill 6 index 6 4 getinterval spill p+v norm perp n v*n p+v 6 aa
    exch pop
    dup 3 2 roll parents 3 1 roll put
    } def


    /snipears {
    find-intersections
    } def

    % O(N**2) Double-loop through the points, comparing segments that are not
    % the same or adjacent. Populate a dict called 'intersections' for any that are found.
    % The keys and values are indices of the found intersections. It is expected that
    % each intersection will show up twice, ie. if segment 23 intersects with 32 then the dict
    % will have << 23 32 >> and << 32 23 >> in there.
    /find-intersections {
    1 dict begin
    {
    dup length 1 ne {
    /path exch def
    /intersections 1 dict def
    2 1 path length 1 sub {
    context path 2 index get get
    p-q-from-context % i p1x p1y q1x q1y
    2 1 path length 1 sub {
    dup 6 index sub dup 2 ge exch -2 le or %0 ne %abs 1 gt
    {
    context path 2 index get get
    p-q-from-context % i p1x p1y q1x q1y j p2x p2y q2x q2y
    8 index 8 index 8 index 8 index
    intersect? % i p1x p1y q1x q1y j bool
    %{ (X) print } if %==
    %{ dup 6 index =only ( )print = } if
    {
    dup 6 index
    2 copy =only ( )print =
    intersections 3 1 roll put
    } if
    } if
    pop
    } for
    pop pop pop pop pop
    } for
    path
    intersections {
    2 copy min 3 1 roll max 1 index sub % i n
    chopsubpath exch
    dup length 1 eq { transformear-line }{ transformear-curve } ifelse
    exch joinsubpath
    exit
    } forall
    } if
    } map
    end
    / =
    } def

    /chopsubpath { 3 1 roll headtail 3 2 roll headtail } def
    /headtail { 2 copy head 3 1 roll tail } def
    /joinsubpath { cat cat } def


    % What to replace the ear with?
    /transformear-line { 1 last 0 get 2 last 1 aa } def

    /transformear-curve { % [1 1] -> [0]
    dup 0 get exch 1 last 0 get % ^[] $[]
    /f exch def /s exch def
    context s get 4 head 2 headtail cvx /sc1 exch def cvx /sc0 exch def
    vectors s get 2 head cvx /sv0 exch def
    context f get 4 last 2 headtail cvx /fc5 exch def cvx /fc4 exch def
    vectors f get 8 2 getinterval cvx /fv4 exch def
    fc4 sc1 p-p 2 aa cvx /nv5 exch def
    sv0 norm nv5 mag .05 mul v*n 2 aa cvx /nv1 exch def
    sc1 nv1 p+v 2 aa cvx /nc2 exch def
    fv4 norm nv5 mag .05 mul v*n 2 aa cvx /nv3 exch def
    fc4 nv3 p-p 2 aa cvx /nc3 exch def
    [ nc2 nc3 fc4 ]
    context 1 index [ sc0 sc1 nc2 nc3 fc4 fc5 ] put
    vectors 1 index [ sv0 nv1 nc3 nc2 p-p nv3 fv4 nv5 ] put
    1 aa
    } def

    /p-q-from-context {
    dup length 6 eq {
    0 4 getinterval spill
    }{
    dup 2 2 getinterval spill 3 2 roll 8 2 getinterval spill
    } ifelse
    } def


    % The main script
    % Use charpath to produce the outline of the number 9 in
    % Calibri 600pt. Scale up to make the problem spot more visible.
    % Attempt various combinations of offsets and offsets of offsets.

    8 dup dup scale currentlinewidth exch div setlinewidth
    /Calibri-Bold 600 selectfont
    10 10 moveto (9) true charpath
    gsave stroke grestore
    %gsave 20 setlinewidth strokepath 1 setlinewidth stroke grestore

    1 0 0 setrgbcolor
    gsave 5 offsetpath stroke grestore
    gsave 10 offsetpath stroke grestore
    %gsave 5 offsetpath 5 offsetpath stroke grestore
    gsave 5 offsetpath 5 offsetpath 5 offsetpath stroke grestore

    showpage quit

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From jdaw1@21:1/5 to All on Thu Jun 4 03:15:47 2020
    As a marker, this is still wanted. But only if it works with total reliability, even on the nasty paths made by charpath, whether the font be a Helvetica, a Garamond, or a Blackletter monstrosity.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From luser droog@21:1/5 to All on Thu Jul 9 10:31:20 2020
    On Thursday, June 4, 2020 at 5:15:48 AM UTC-5, jdaw1 wrote:
    As a marker, this is still wanted. But only if it works with total reliability, even on the nasty paths made by charpath, whether the font be a Helvetica, a Garamond, or a Blackletter monstrosity.

    The path forward appears to me to be in that Bezier curve classification
    paper. My code has all the infrastructure to run through the path,
    gather lots of context points for each curve, calculate vectors and
    normals and offsets of the control points. But it's missing some
    heuristics to detect problem curves and deal with them, probably by
    chopping into 2 simpler curves.

    But if we can analyze the curve and detect whether there's a cusp
    in the middle and *where* on the curve it is, then we can chop the
    curve into two easier pieces and a few recursive steps should yield
    an offset curve with better fine tracking. ... Or maybe better not
    to chop and find an intersection between offsets of the two vectors.
    I'm not sure which track will yield better results.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From jdaw1@21:1/5 to All on Sat Feb 13 09:51:02 2021
    Cross-link: https://github.com/jdaw1/placemat/issues/18

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)