{"id":16953913,"url":"https://github.com/romainguy/combo-breaker","last_synced_at":"2025-04-08T00:36:22.795Z","repository":{"id":63390005,"uuid":"563137331","full_name":"romainguy/combo-breaker","owner":"romainguy","description":"Text layout for Compose to flow text around arbitrary shapes.","archived":false,"fork":false,"pushed_at":"2024-10-27T18:49:57.000Z","size":4415,"stargazers_count":586,"open_issues_count":4,"forks_count":19,"subscribers_count":9,"default_branch":"main","last_synced_at":"2024-11-14T09:08:41.745Z","etag":null,"topics":["android","jetpack-compose","kotlin-android","text","text-layout"],"latest_commit_sha":null,"homepage":"","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/romainguy.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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-11-08T01:27:56.000Z","updated_at":"2024-11-09T02:11:50.000Z","dependencies_parsed_at":"2023-12-13T03:55:58.169Z","dependency_job_id":"60c4dab3-aa41-4589-819d-20dc58282c84","html_url":"https://github.com/romainguy/combo-breaker","commit_stats":null,"previous_names":[],"tags_count":10,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/romainguy%2Fcombo-breaker","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/romainguy%2Fcombo-breaker/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/romainguy%2Fcombo-breaker/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/romainguy%2Fcombo-breaker/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/romainguy","download_url":"https://codeload.github.com/romainguy/combo-breaker/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247755560,"owners_count":20990620,"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":["android","jetpack-compose","kotlin-android","text","text-layout"],"created_at":"2024-10-13T22:08:18.236Z","updated_at":"2025-04-08T00:36:22.754Z","avatar_url":"https://github.com/romainguy.png","language":"Kotlin","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Combo Breaker\n\n[![combo-breaker](https://maven-badges.herokuapp.com/maven-central/dev.romainguy/combo-breaker/badge.svg?subject=combo-breaker)](https://maven-badges.herokuapp.com/maven-central/dev.romainguy/combo-breaker)\n[![combo-breaker-material3](https://maven-badges.herokuapp.com/maven-central/dev.romainguy/combo-breaker-material3/badge.svg?subject=combo-breaker-material3)](https://maven-badges.herokuapp.com/maven-central/dev.romainguy/combo-breaker-material3)\n[![Android build status](https://github.com/romainguy/combo-breaker/workflows/Android/badge.svg)](https://github.com/romainguy/combo-breaker/actions?query=workflow%3AAndroid)\n\nComposable widget for Jetpack Compose that allows to flow text around arbitrary shapes over\nmultiple columns. The `TextFlow` composable behaves as a `Box` layout and will automatically\nflow the text content around its children.\n\n* [Features](#features)\n* [Design Systems](#design-systems)\n* [Examples](#examples)\n* [Maven](#maven)\n* [Roadmap](#roadmap)\n* [License](#license)\n* [Attribution](#attribution)\n\n## Features\n\n- Multi-column layout\n- Styled strings (`AnnotatedString`)\n- Default rectangular shapes\n- Arbitrary shapes (any `Path`)\n- Justification\n- Hyphenation\n- Compatible with API 29+\n\n## Design Systems\n\nCombo Breaker provides two levels of APIs depending on what design system you use:\n\n- `BasicTextFlow` from the `dev.romainguy:combo-breaker` artifact, which works with any design system\n- `TextFlow` from the `dev.romainguy:combo-breaker-material3` artifact, which works with Material3\n\nChoose `BasicTextFlow` if you do not have or do not want a dependency on\n`androidx.compose.material3:material3`.\n\n## Examples\n\nThe following code defines two images to flow text around:\n\n```kotlin\nTextFlow(\n    SampleText,\n    style = TextStyle(fontSize = 14.sp),\n    columns = 2\n) {\n    Image(\n        bitmap = letterT.asImageBitmap(),\n        contentDescription = \"\",\n        modifier = Modifier\n            .flowShape(FlowType.OutsideEnd)\n    )\n\n    Image(\n        bitmap = badgeBitmap.asImageBitmap(),\n        contentDescription = \"\",\n        modifier = Modifier\n            .align(Alignment.Center)\n            .flowShape(margin = 6.dp)\n    )\n}\n```\n\n![Flow around rectangular shapes](art/screenshot_default_shapes.png)\n\nAny child of `TextFlow` allows text to flow around a rectangular shape of the same dimensions of the\nchild. The `flowShape` modifier is used to control where text flows around the shape (to the\nright/end of the T) and around both the left and right sides of the landscape photo (default\nbehavior). In addition, you can define a margin around the shape.\n\nThe `flowShape` modifier also lets you specify a specific shape instead of a default rectangle.\nThis can be done by passing a `Path` or a lambda that returns a `Path`. The lambda alternative\nis useful when you need to create a `Path` based on the dimensions of the `TextFlow` or the\ndimensions of its child.\n\nHere is an example of a `TextFlow` using non-rectangular shapes:\n\n```kotlin\nval microphoneShape = microphoneBitmap.toPath(alphaThreshold = 0.5f).asComposePath()\nval badgeShape = badgeShape.toPath(alphaThreshold = 0.5f).asComposePath()\n\nTextFlow(\n    SampleText,\n    style = TextStyle(fontSize = 14.sp),\n    columns = 2\n) {\n    Image(\n        bitmap = microphoneBitmap.asImageBitmap(),\n        contentDescription = \"\",\n        modifier = Modifier\n            .offset { Offset(-microphoneBitmap.width / 4.5f, 0.0f).round() }\n            .flowShape(FlowType.OutsideEnd, 6.dp, microphoneShape)\n    )\n\n    Image(\n        bitmap = badgeBitmap.asImageBitmap(),\n        contentDescription = \"\",\n        modifier = Modifier\n            .align(Alignment.Center)\n            .flowShape(FlowType.Outside, 6.dp, badgeShape)\n    )\n}\n```\n\nThe non-rectangular `Path` shape is created using the extension `Bitmap.toPath` from the\n[pathway](https://github.com/romainguy/pathway) library. Using that API, a shape can be extracted\nfrom a bitmap and used as the flow shape for the desired child:\n\n![Flow around non-rectangular shapes](art/screenshot_arbitrary_shapes.png)\n\n`TextFlow` supports multiple text styles and lets you control justification and hyphenation. In\nthe example below, both justification and hyphenation are enabled:\n\n![Justification and hyphenation](art/screenshot_styles_and_justification.png)\n\nYou can also specify multiple shapes for any given element by using the `flowShapes` modifiers\ninstead of `flowShape`. `flowShapes` accepts/returns list of paths instead of a single path.\nFor instance, with [pathway](https://github.com/romainguy/pathway) you can easily extract a list of\npaths from a `Bitmap` by using `Bitmap.toPaths()` instead of `Bitmap.toPath()`.\n\n```kotlin\nval heartsShapes = heartsBitmap.toPaths().map { it.asComposePath() }\n\nTextFlow(\n    SampleText,\n    style = TextStyle(fontSize = 12.sp),\n    columns = 2\n) {\n    Image(\n        bitmap = heartsBitmap.asImageBitmap(),\n        contentDescription = \"\",\n        modifier = Modifier\n            .align(Alignment.Center)\n            .flowShapes(FlowType.Outside, 4.dp, heartsShapes)\n    )\n}\n```\n\nThis creates many shapes around which the text can flow:\n\n![Multiple shapes per element](art/screenshot_shapes.png)\n\n## Maven\n\n```gradle\nrepositories {\n    // ...\n    mavenCentral()\n}\n\ndependencies {\n    // Use this library and BasicTextFlow() if you don't want a dependency on material3\n    implementation 'dev.romainguy:combo-breaker:0.9.0'\n\n    // Use this library and TextFlow() if you use material3\n    implementation 'dev.romainguy:combo-breaker-material3:0.9.0'\n}\n```\n\n## Roadmap\n\n- Backport to earlier API levels.\n- Lines containing styles of different line heights can lead to improper flow around certain shapes.\n- More comprehensive `TextFlowLayoutResult`.\n- Add support to ellipsize the last line when the entire text cannot fit in the layout area.\n- Add support for text-relative placement of flow shapes.\n- Implement margins support without relying on `Path.op` which can be excessively expensive with\n  complex paths.\n- BiDi text hasn't been tested yet, and probably doesn't work properly (RTL layouts are however\n  supported for the placement of flow shapes and the handling of columns).\n- Improve performance of contours extraction from an image (could be multi-threaded for instance).\n- Investigate an alternative and simpler way to handle placement around shapes (beam cast instead \n  of the purely geometric approach that currently requires a lot of intersection work).\n- Support flowing text inside shapes.\n\n## License\n\nPlease see [LICENSE](./LICENSE).\n\n## Attribution\n\nThe render of the microphone was made possible thanks to\n[RCA 44-BX Microphone](https://skfb.ly/6AKHx) by Tom Seddon, licensed under\n[Creative Commons Attribution](http://creativecommons.org/licenses/by/4.0/).\n\nSample text taken from the [Wikipedia Hyphen article](https://en.wikipedia.org/wiki/Hyphen).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fromainguy%2Fcombo-breaker","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fromainguy%2Fcombo-breaker","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fromainguy%2Fcombo-breaker/lists"}