{"id":13751846,"url":"https://github.com/chfoo/cobbletext","last_synced_at":"2025-05-15T16:33:30.342Z","repository":{"id":145909467,"uuid":"254995702","full_name":"chfoo/cobbletext","owner":"chfoo","description":"Complex text layout and rendering engine library","archived":false,"fork":false,"pushed_at":"2025-05-05T22:14:04.000Z","size":368,"stargazers_count":51,"open_issues_count":3,"forks_count":4,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-05-05T22:28:06.199Z","etag":null,"topics":["bindings","layout-engine","text-rendering"],"latest_commit_sha":null,"homepage":null,"language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mpl-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/chfoo.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2020-04-12T02:42:40.000Z","updated_at":"2025-05-05T22:14:07.000Z","dependencies_parsed_at":null,"dependency_job_id":"3011f0b9-68b5-40e9-b84b-4d67cb8df8d0","html_url":"https://github.com/chfoo/cobbletext","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chfoo%2Fcobbletext","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chfoo%2Fcobbletext/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chfoo%2Fcobbletext/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chfoo%2Fcobbletext/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/chfoo","download_url":"https://codeload.github.com/chfoo/cobbletext/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254377496,"owners_count":22061156,"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","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":["bindings","layout-engine","text-rendering"],"created_at":"2024-08-03T09:00:55.786Z","updated_at":"2025-05-15T16:33:30.335Z","avatar_url":"https://github.com/chfoo.png","language":"C++","readme":"# Cobbletext\n\nCobbletext is a complex text layout and rendering engine as a C/C++ library. It bundles formatting, bidirectional analysis, shaping, line breaking, and glyph rasterization into one simple interface. It is not tied to any specific operating system framework and is designed for general purpose use on graphics contexts such as OpenGL, SDL, or HTML5 Canvas.\n\nIt uses FreeType, HarfBuzz, and Unicode ICU for the actual dirty work.\n\nThe project is considered a \"toy\" library; it works \"good enough\" but does not aim to match the level of completeness, performance, and development of an actively developed engine.\n\nOnline demos:\n\n* [js_example](https://chfoo.github.io/cobbletext/example/js_example/) JavaScript with Emscripten bindings. Note: requires 30 MB of data download due to fonts.\n\n## Quick start\n\nSee [INSTALL.md](INSTALL.md) for building the library.\n\nFor prebuilt libraries, check the Releases section in the GitHub repository. Prebuilt libraries might not always work. Check the troubleshooting section for details.\n\nOn macOS, please define `COBBLETEXT_NO_UCHAR_H`.\n\n### Prepare context\n\nBefore any text processing can be done, a library context instance needs to be created:\n\n```c++\n// C\n#include \u003ccobbletext/cobbletext.h\u003e\nCobbletextLibrary library * = cobbletext_new_library();\n\n// C++\n#include \u003ccobbletext/cobbletext.hpp\u003e\nauto library = std::make_shared\u003ccobbletext::Library\u003e();\n```\n\nOnly one library context is required. However, for threading situations, a library context can be created per thread since Cobbletext and its dependencies is not thread-safe.\n\n### Error handling\n\nWhenever there is an error, an exception is thrown. On C, the error can be retrieved like so:\n\n```c++\nint32_t error_code = cobbletext_get_error_code(library);\nconst char * error_message = cobbletext_get_error_message(library);\n```\n\nThis tutorial will omit error handling for brevity.\n\n### Loading fonts\n\nTo draw any useful text, a font file must be specified:\n\n```c++\n// C\nCobbletextFontID myFont = cobbletext_library_load_font(library, \"path_to_font.ttf\");\n\n// C++\ncobbletext::FontID myFont = library-\u003eloadFont(\"path_to_font.ttf\");\n```\n\nEach font face needs to be loaded individually. The returned ID allows you to select that font later. (At this time, it's not possible to select loaded fonts by name and style.)\n\n### Create engine\n\nA layout engine is created like so:\n\n```c++\n// C\nCobbletextEngine * engine = cobbletext_engine_new(library);\n\n// C++\nauto engine = cobbletext::Engine(library);\n```\n\nAn engine is used repeatedly to process text. It holds a text buffer and properties for runs of text.\n\nMultiple engines can be created for specific purposes. For example, one engine can be dedicated for the application GUI elements and another engine can be dedicated to chat room messages.\n\n### Set engine properties\n\nOnce the engine is created, you can apply some settings that control its output:\n\n```c++\n// C\nstruct CobbletextEngineProperties properties = {0};\nproperties.line_length = 500;\nproperties.locale = \"en-US\";\ncobbletext_engine_set_properties(engine, \u0026properties);\n\n// C++\nengine.lineLength = 500;\nengine.locale = \"en-US\";\n```\n\n### Set text properties\n\nBefore adding text, set some basic text formatting. These properties will be applied to the subsequent runs of text:\n\n```c++\n// C\nstruct CobbletextTextProperties text_properties = {0};\ntext_properties.font = myFont;\ntext_properties.font_size = 16;\ncobbletext_engine_set_text_properties(engine, \u0026text_properties);\n\n// C++\nengine.font = myFont;\nengine.fontSize = 16;\n```\n\n### Adding text\n\nFinally, we can add some text:\n\n```c++\n// C\ncobbletext_engine_add_text_utf8(engine, \"Hello world!\", -1);\n\n// C++\nengine.addTextUTF8(\"Hello world!\");\n```\n\n### Do lay out\n\nOnce all the text has been added, we can perform processing of the text:\n\n```c++\n// C\ncobbletext_engine_lay_out(engine);\n\n// C++\nengine.layOut();\n```\n\n### Process tiles\n\nNow the text has been processed, the results are stored as tiles and advances. Tiles represent images of each glyphs associated to the given text and advances represent the pen instructions to draw tiles.\n\nFirst, we'll convert the glyphs to images:\n\n```c++\n// C\ncobbletext_engine_rasterize(engine);\n\n// C++\nengine.rasterize();\n```\n\nThen, we'll arrange the tiles into a texture atlas:\n\n```c++\n// C\nbool has_overflow = cobbletext_engine_pack_tiles(engine, 256, 256);\n\n// C++\nbool hasOverflow = engine.packTiles(256, 256);\n```\n\nThe tile packing function will return a value indicating that the tiles did not fit within the texture. To handle the case where it does not fit, double the texture size. If you aren't using a texture atlas, you can skip the texture atlas step.\n\nNow get the tiles:\n\n```c++\n// C\ncobbletext_engine_prepare_tiles(engine);\nuint32_t tile_count = cobbletext_engine_get_tile_count(engine);\nconst struct CobbletextTileInfo ** tile_array cobbletext_engine_get_tiles(engine);\n\n// C++\nstd::vector\u003cTileInfo\u003e tiles = engine.tiles();\n```\n\nDraw the images to the texture atlas, and store the atlas metadata to a hash table:\n\n```c++\n// C\nfor (uint32_t index; index \u003c tile_count; index++) {\n    const struct CobbletextTileInfo * tile = tile_array[index];\n    const struct CobbletextGlyphInfo * glyph = cobbletext_library_get_glyph_info(library, tile-\u003eglyph_id);\n\n    my_draw_to_atlas(\n        glyph-\u003eimage, glyph-\u003eimage_width, glyph-\u003eimage, // Source image data\n        tile-\u003eatlas_x, tile-\u003eatlas_y // Destination on texture\n    );\n    my_atlas_table_set(\n        tile-\u003eglyph_id, // Key\n        tile-\u003eatlas_x, tile-\u003eatlas_y, // Location on atlas\n        glyph-\u003eimage_offset_x, glyph-\u003eimage_offset_y // Drawing metadata\n    );\n}\n\n// C++\nfor (auto \u0026 tile : tiles) {\n    auto glyph = library.getGlyphInfo(tile.glyphID);\n\n    myDrawToAtlas(\n        glyph.image, glyph.imageWidth, glyph.imageHeight, // Source\n        tile.atlasX, tile.atlasY // Destination\n    );\n    myAtlas.set(\n        tile.glyphID, // Key\n        tile.atlasX, tile.atlasY, // Location on atlas\n        glyph.imageOffsetX, glyph.imageOffsetY // Drawing metadata\n    );\n}\n```\n\nIn order to not process image data to every time text is changed, use:\n\n```c++\n// C\nbool isValid = cobbletext_engine_tiles_valid(engine);\n\n// C++\nbool isValid = engine.tilesValid();\n```\n\nFor a texture atlas, this function can be used to determine whether a texture atlas should be rebuilt with new additional tiles. If the tiles are not valid, then:\n\n1. Clear texture.\n2. Call \"rasterize\" function.\n3. Call \"pack tiles\" function.\n4. Process tiles by adding or replacing tile metadata.\n5. Draw the glyphs to the texture.\n\n### Prepare destination image\n\nThen we prepare a destination image:\n\n```c++\n// C\nconst struct CobbletextOutputInfo * output_info = cobbletext_engine_get_output_info(engine);\nMyImage * image = my_make_image(output_info-\u003etext_width, output_info-\u003etext_height);\n\n// C++\nauto image = MyImage(engine.outputInfo.textWidth, engine.outputInfo.textHeight);\n```\n\n### Drawing advances\n\nNow we can draw the text. First we get the advances:\n\n```c++\n// C\ncobbletext_prepare_advances(engine);\nuint32_t advance_count = cobbletext_engine_get_advance_count()\nconst struct CobbletextAdvanceInfo ** advances_array = cobbletext_engine_get_advances(engine);\n\n// C++\nstd::vector\u003ccobbletext::AdvanceInfo\u003e advances = engine.advances();\n```\n\nThen iterate through each advance, drawing glyphs when needed:\n\n```c++\n// C\nint32_t pen_x = 0;\nint32_t pen_y = 0;\n\nfor (int32_t index; index \u003c advance_count; index++) {\n    const struct CobbletextAdvanceInfo * advance = advances_array[index];\n\n    switch (advance-\u003etype) {\n        case COBBLETEXT_ADVANCE_TYPE_GLYPH:\n            MyAtlasEntry * entry = my_atlas_table_get(advance-\u003eglyph_id);\n\n            int32_t x = pen_x + advance-\u003eglyph_offset_x + entry-\u003eimage_offset_x;\n            int32_t y = pen_y + advance-\u003eglyph_offset_x + entry-\u003eimage_offset_x;\n\n            my_image_draw_tile(image,\n                entry-\u003eatlas_x, entry-\u003eatlas_y, // Source position\n                x, y // Destination position\n            );\n            break;\n    }\n\n    pen_x += advance-\u003eadvance_x;\n    pen_y += advance-\u003eadvance_y;\n}\n\n// C++\nint32_t penX = 0;\nint32_t penY = 0;\n\nfor (auto \u0026 advance : advances) {\n    switch (advance-\u003etype) {\n        case cobbletext::AdvanceType::Glyph:\n            auto \u0026 entry = myAtlas.get(advance-\u003eglyphID);\n\n            int32_t x = penX + advance-\u003eglyphOffsetX + entry-\u003eimageOffsetX;\n            int32_t y = penY + advance-\u003eglyphOffsetY + entry-\u003eimageOffsetX;\n\n            image.myDrawTile(\n                entry-\u003eatlasX, entry-\u003eatlasY, // Source position\n                x, y // Destination position\n            );\n            break;\n    }\n\n    penX += advance-\u003eadvanceX;\n    penY += advance-\u003eadvanceY;\n}\n```\n\nThe text is finally rendered!\n\n### Reuse engine\n\nTo reuse the engine, use the clear function which will remove the text from its internal buffer, but keep all properties and tiles intact:\n\n```c++\n// C\ncobbletext_engine_clear(engine);\n\n// C++\nengine.clear();\n```\n\n### Clearing tiles\n\nIf your texture atlas for an engine is filling up, instead of deleting and recreating an engine entirely, you can clear the tiles and the associated glyphs.\n\n```c++\n// C\ncobbletext_engine_clear_tiles(engine);\n\n// C++\nengine.clearTiles();\n```\n\nThen, you proceed as if you have a blank texture atlas by creating a new texture and atlas metadata hash table, or clear the texture and hash table.\n\n## Troubleshooting libraries\n\nIf you have trouble getting the libraries to work at runtime, ensure the libraries are located on the OS's search path. As well ensure the library's dependencies are also working.\n\n### Windows\n\nLibraries can be placed in the same directory as the executable. If library refuses to load despite it being there, check that its dependencies are found using Dependency Walker or Process Monitor. You might need the latest Visual C++ Redistributable.\n\n### MacOS\n\nPlacing libraries in the same directory as the executable might not work. You can temporarily specify a search path using the `DYLD_FALLBACK_LIBRARY_PATH` environment variable. For example: `DYLD_FALLBACK_LIBRARY_PATH=my/path/to/dylib/directory/:/usr/local/lib:/lib:/usr/lib ./my_application`. Note that macOS strips this environment variable on child processes so you can't `export` it.\n\nUse `otool` to inspect your application's library dependencies. The libraries may be linked into a hardcoded path which may not exist on another system. Or the libraries use `@rpath/` prefix but your application does not specify an rpath. Use `install_name_tool` to change the names.\n\n### Linux\n\nYou can temporarily include library paths using the `LD_LIBRARY_PATH` environment variable. For example: `LD_LIBRARY_PATH=my/path/to/so/directory/ ./my_application`. The `ldd` command and setting the environment variable `LD_DEBUG=libs` can be useful.\n\n## Further reading\n\n* Please see the examples in the example directory.\n* [API documentation](https://chfoo.github.io/cobbletext/doc/api/)\n* [HarfBuzz documentation](https://harfbuzz.github.io/index.html)\n* [FreeType documentation](https://www.freetype.org/freetype2/docs/documentation.html)\n* [ICU documentation](http://userguide.icu-project.org/)\n\n## Supported features\n\n| Feature | Is supported? |\n|---------|---------------|\n| Text transform (capitalization) | No |\n| Line breaking (mandatory, word wrapping) | Yes |\n| Alignment | Yes |\n| Justification | No |\n| Bidirectional text | Yes |\n| Vertical text | No |\n| Font name matching (family, style, weight, etc.) | No |\n| Font fallback | Yes (partial) |\n| Language-specific display | Yes |\n| Kerning | Yes |\n| Ligatures | Yes |\n| Subscript \u0026 superscript | No |\n| Capitalization, numerical formatting | No |\n| Ruby | No |\n| Line decoration (underline, overline, strike-through) | No |\n| Emphasis marks | No |\n| Cursor positioning by index | Yes |\n| Cursor positioning by XY-coordinate | No |\n\n## Contributing\n\nIf you have any issues or improvements, please use the GitHub Issues and Pull Requests section.\n\n## License\n\nCopyright 2020 Christopher Foo. Licensed under Mozilla Public License Version 2.0.\n\nDependencies used within the library:\n\n* Microsoft GSL: MIT license\n* Boost: BSD-like license\n* STB: MIT license\n* FreeType: BSD-like license or GPLv2\n  * Bzip2/libbz2: BSD-like license\n  * Libpng: BSD-like license\n  * zlib: BSD-like license\n* HarfBuzz: MIT license\n* ICU/ICU4C: BSD-like license\n* Adobe NotDef: SIL Open Font license\n","funding_links":[],"categories":["C++"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchfoo%2Fcobbletext","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fchfoo%2Fcobbletext","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchfoo%2Fcobbletext/lists"}