{"id":24886370,"url":"https://github.com/mzguntalan/vegetable","last_synced_at":"2025-08-09T23:32:58.221Z","repository":{"id":43916872,"uuid":"470454588","full_name":"mzguntalan/vegetable","owner":"mzguntalan","description":"Vegetable contains a design/definition of a Vector Graphic that allows it to easily render it as equally an spaced point cloud/sequence. From this, vegetable offers a way to read .ttf font files, and render their glyphs into point clouds/sequences.","archived":false,"fork":false,"pushed_at":"2022-07-08T04:50:18.000Z","size":1521,"stargazers_count":2,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-27T10:30:23.834Z","etag":null,"topics":["data-preparation","data-science","point-cloud","python","svg","ttf","vector-graphics"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mzguntalan.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2022-03-16T06:08:17.000Z","updated_at":"2023-02-01T23:20:48.000Z","dependencies_parsed_at":"2022-09-22T08:32:13.737Z","dependency_job_id":null,"html_url":"https://github.com/mzguntalan/vegetable","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/mzguntalan/vegetable","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mzguntalan%2Fvegetable","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mzguntalan%2Fvegetable/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mzguntalan%2Fvegetable/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mzguntalan%2Fvegetable/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mzguntalan","download_url":"https://codeload.github.com/mzguntalan/vegetable/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mzguntalan%2Fvegetable/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":269653714,"owners_count":24454315,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-09T02:00:10.424Z","response_time":111,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["data-preparation","data-science","point-cloud","python","svg","ttf","vector-graphics"],"created_at":"2025-02-01T15:14:51.062Z","updated_at":"2025-08-09T23:32:58.158Z","avatar_url":"https://github.com/mzguntalan.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# vegetable\nAll about turning vector graphics into a set of points.\n\nRemark:\n- required packages not yet specified :p,(but it's numpy and fontTools). \n\nMotivation:\n- machine learning on vector graphics using point cloud representations\n\nThis is a B rendered from 0 points to 128 points.\n\n![animated-B-going-from-0-to-128-points](demo/animated_B.gif)\n\nThese are glyphs of a certain font rendered as a point sequence.\n\n![animated-B-going-from-0-to-128-points](demo/glyph_as_point_sequence.png)\n\n\n# carrot\nThis package contains a basis for handling vector graphics. \nIt provides the powerful class called `VectorGraphic` which is\ndefined by:\n- starting point: Tuple[float, float]\n- ending point: Tuple[float, float]\n- function: Callable[[float], Tuple[float, float]]:\n  - this is a function that takes in a float and outputs the correspoinding point\n  - f_t : it can either be a generic parameterization f(t) where t is not remarkable; or\n  - f_portion_s: it can be f(s/L) where s is the arc length and L is a constant that refers to the length of this vector graphic\n  - while you can specify both, it only keeps `f_portion_s`, if you pass in a `f_t`\n- number of points for approximating this vector graphic as a point cloud/sequence: int\n\nThis class can have any weird f_t/f_portion_s as you like, but many times, you would like to use simpler segments and \ncompose them into more complicated graphics. For this you have SVG inspired components: Line, QuadraticCurve \nprovided by `carrot`. They have ready `f_t`/`f_portion_s` that they pass to VectorGraphic and they themselves are \nimplementations of VectorGraphic. Do note that VectorGraphic is not an abstract class and can be used directly (if you \ncan write down their parameterized functions). \n\n## Demo\nThis is how you create a vector graphic. Here we create them using the `VectorGraphic` class and the `QuadraticCurve` class.\n```python\nfrom carrot.vector_graphic import VectorGraphic\nfrom carrot.svg import QuadraticCurve\n\nvg = VectorGraphic(\n  start_point=(0,1),\n  end_point=(1,0),\n  f_t=lambda t: (t, 1-t),\n  num_points_for_approximation=10\n)\n\ncurve = QuadraticCurve(\n  start_point=(1,0),\n  end_point=(2,3),\n  control_point=(2.5, 1.5),\n  num_points_for_approximation=10\n)\n```\n\nA vector graphic has some highlight methods\n- `get_as_point_sequence()` which returns an iterator of points equal to that passed in at the constructor(`num_points_for_approximation`)\n- `get_approximate_length()` which returns the length of this vector graphic\n- `__call__(portion_s)` which accepts a number in `[0,1]` which denotes `s/L` ~ `arc_length/total_length`\n\nOne thing you might want to do is get the vector graphic as a sequence of points.\n```python\nvg_points: List[Tuple[float, float]] = list(vg.get_as_point_sequence())\n```\n\nIf you like them to be a numpy array, you can do the following.\n```python\nimport numpy as np\nvg_points: np.ndarray = np.array(list(vg.get_as_point_sequence()))\n```\n\n### Composing Vector Graphics from smaller ones\nThe neat thing about these, is you can add them together into one vector graphic. \n\n```python\nnew_vg = vg + curve\n```\n\nThe result is itself a vector graphic.\nNo the result is not a collection of the two - there is no list that collects the pieces together - the new vector graphic\nis itself a `VectorGraphic` object which means it defines its shape by `f_portion_s`; this means that it doesn't know \nit was composed of smaller segments and it acts just like any other vector graphic. \n\nAnother cool feature is that you don't need the smaller pieces to be connected (connected means: end point of the first == start point of the second). \nSo you can create entire logos, figures, with all just 1 vector graphic object.\n\nThis feature wherein the sum of 2 vector graphics is also a vector graphic allows it to be rendered as a point sequence where\nthe adjacent points are equally spaced.\n\n# broccoli\nThis package uses `carrot` in order to deliver the following.\n- a TrueType `ttf` font reader that has a `read_font(...)` method that returns a `Font` object\n- `Font` class that has a name, and a `__call__(glyph_name)` that returns a `Glyph` object if the glyph exists, and raises a `KeyError`, otherwise.\n- `Glyph` class which is a `VectorGraphic` object and has an additional name property\n\n## Motivation\n`broccoli` was made to fill the needs that I had in the past, for which I could'nt find good solutions for. The solutions i had were hard to engineer and has perfomance issues because it was wildly different solutions stuck together - even then, the resulting point clouds were not as neat. \n\nThe TTF Reader that broccoli provides can read a ttf font file for 52 glyphs (a-z, A-Z) in about `0.5s`. This reader can also serves as an example how the `carrot` package can be used - other use cases can be `otf` font files, `svg` files, and others. \n\n## Demo\nSay you have a `ttf` file somewhere and you want to get the glyphs A,B,a,b and you want to make point clouds out of them.\n\nYou first read the font to the get a `Font` object back. After that, you can access the glyphs in the font.\n```python\nfrom broccoli.ttf.ttf_reader import TTFReader\nfont_file_path = \"/somewhere/out/there/font_name-italic.ttf\"\nglyphs_you_want: str = \"ABab\"\nreader = TTFReader(glyph_names_to_read_in_font_files=glyphs_you_want, num_points_for_glyph_as_sequence=128, num_points_for_internal_approximation=2)\nfont = reader.read_font(font_file_path)\n\n# get the glyph you want\npoint_cloud = {}\nfor glyph_name in glyphs_you_want:\n    glyph = font(glyph_name)\n    # remember that glyph is a VectorGraphic\n    point_cloud[glyph.name] = list(glyph.get_as_point_sequence())\n```\n\nAdditionally, you can make numpy arrays from them with the coordinates scaled from -1 to 1 (which is useful for machine learning).\n```python\nfrom broccoli.utils.font_to_numpy import font_to_numpy\n\nfont_as_numpy_array = font_to_numpy(font)\n```\n\n### Insight to how this reader works\nThis reader works by reading the `ttf` and finding the `ttglyfs` that you specified. For each of those glyph specification it then finds the contours of a glpyh. Each contour is a sequence of lines or\ncurves (encoded as a sequence of on and off points) and each of those is converted as a `VectorGraphic` object. \nThen, the glyph is then just the sum of all these `VectorGraphic` objects.\n\n# Technical details for `carrot`\n## Reparameterization to `portion_s`\nGiven a parametrization of a curve in 2d: `f(t:float)=(x,y)` where t in `[0,1]`, we first derive `g(s)` where `s` is the arc length - reparameterization to arc length. To do this we need to solve for `t` in terms of `s` then replace t in `f(t)` to get a new function `g(s)=f(t_in_terms_of_s)`. \n\nThe arc length from `t=0` to some `t=t` is given by the integral below\n$ s(t) = \\int_0^t |f'(t)| dt $\n\nAt this, point, we should realize that for a line, the above integral is easy, but even for a parabola (a quadratic curve) it has no closed form, and therefore t in terms of s also has no closed form.\n\nTo continue, we should realize another way of computing for the arc length - subdivide the curve into many many linear pieces and add the lengths together (this is an interpretation of the integral above).We have `f(t)` at our disposal, which means if we wanted to compute the arc length for 0 to `T` we can calculate points: `f(0), f(0.01),...f(T)` with as many points as we like (more points yield more accuracy) and compute the lengths between pairs of adjacent points and sum the lengths together. \n\nThis means given `t` we can compute `s`, but we would like to know `t` in terms of `s` - in other words, if i has `s`, i want to know `t`. \nTo do this we do a binary search, given `S` we search for `t` such that `s(t) - S ~=0`. This now gives us a function `t(s)` or `t` given `s`. \n\nWith this to derive `g(s)` we simply compose `t(s)` and `f(t)` together to form `g(s)=f(t(s))`.\n\nNow that we have reparameterized `f` with `s` as the arc length, we now proceed to reparameterizing in terms of `portion_s` which is simply `portion_s=s/L` where L is the length of the segment (computed above calculating the arc length from `t=0` to `t=1`). Solving for `s` we have `s=portion_s/L`.\n\nCombining all of these yields: `h(portion_s)=g(portion_s*L)=f(t(portion_s*L))`, for simplicity we just call this `f(portion_s)`.\n\n## Adding of Vector Graphics\nAt this point, we can think of a `VectorGraphic` object as simply `f(portion_s)`. If we have two vector graphics `left` and `right` that we want to add as `sum = left + right` (addition does not commute for vector graphics), we can come up with a reasonable way to combine them on the basis of their lengths. \n\nRemember that `portion_s` will be inside `[0,1]` for any vector graphic. An intuitive addition for `left` and `right` is that: \n- `sum(0)` to `sum(m)` produces `left(0)` to `left(1)`\n- `sum(m)` to `sum(1)` produces `right(0)` to `right(1)`\nIn words, this means, that if you travel along `sum`, you will first travel along `left` then at some point you switch to traveling along `right`. \n\nWith this, we can now define the function for `sum` (i use `z` for a generic variable, we can worry if it is `s`,`t`, or `portion_s` later):\n```\nsum(z) = left(k(z)) if z in [0,m]\n         right(j(z)) else\n```\nwhere `k,j` are functions to map `z` to `portion_s` of `left` and `right`, respectively.\n\nWe now determine an intuitive `m`. If `left` was just as long as `right`, then traveling along `sum` (at constant speed), you would expect to reach the end of `left` half way way through the journey. Which means `sum(0.5)=left(1)=sum(m)`. In another case, if `left` was a quarter of the whole journey, then `m=0.25` would be intuitive. Generically, `m` is the length of `left` over the total length of `sum`. \n\n`m = len(left)/(len(left)+len(right))`\n\nRight now it is helpful to notice that we are intuitively passing `portion_s` in `sum`, so `z` will be `portion_s`.\n\nThe last part is figuring out `k` and `j`. \n\nWe know that `k(0) = 0` and `k(m)=1`, a natural(linear) solution would just be `k(z) = z/m`, then in a similar fashion we set `j(z) = (z-m)/(1-m)` (j(m)=0, j(1)=1).\n\nFinally, putting them together:\n```\nsum(z) = left(z/m) if z in [0,m]\n         right((z-m)/(1-m)) else\n```\nwhere `z` is `portion_s` and `m=len(left)/(len(left)+len(right))`.\n\n# Author Details and Citing this project\nIf you use this project, please cite it as follows. Thank you.\n\n```shell\n@software{Untalan_Vector_Graphics_Vegetable_2022,\n  author = {Untalan, Marko Zolo G.},\n  title = {{Vegetable}},\n  url = {https://github.com/mzguntalan/vegetable},\n  year = {2022}\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmzguntalan%2Fvegetable","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmzguntalan%2Fvegetable","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmzguntalan%2Fvegetable/lists"}