Initial commit

This commit is contained in:
2025-02-10 03:05:25 +08:00
commit 57a0ecc385
93 changed files with 6685 additions and 0 deletions

4
docs-source/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
/node_modules
/src/.vuepress/.cache
/src/.vuepress/.temp
/dist

3
docs-source/.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,3 @@
{
"git.ignoreLimitWarning": true
}

View File

@@ -0,0 +1,4 @@
cd ..
./gradlew :pangutext-android:publishKDoc
# TODO: When the pangutext-compose library is done.
# :pangutext-compose:publishKDoc

17
docs-source/package.json Normal file
View File

@@ -0,0 +1,17 @@
{
"name": "pangutext_docs",
"license": "Apache-2.0",
"devDependencies": {
"@mr-hope/vuepress-plugin-copy-code": "^1.30.0",
"@vuepress/plugin-prismjs": "2.0.0-rc.0",
"@vuepress/plugin-search": "2.0.0-rc.0",
"@vuepress/plugin-shiki": "2.0.0-rc.0",
"vuepress": "2.0.0-rc.0"
},
"scripts": {
"docs:dev": "vuepress dev src",
"docs:build": "vuepress build src",
"docs:build-gh-pages": "vuepress build src && touch dist/.nojekyll && sh build-dokka.sh"
},
"dependencies": {}
}

View File

@@ -0,0 +1,64 @@
import { defaultTheme } from 'vuepress';
import { shikiPlugin } from '@vuepress/plugin-shiki';
import { searchPlugin } from '@vuepress/plugin-search';
import { navBarItems, sideBarItems, configs, pageLinkRefs } from './configs/template';
import { env, markdown } from './configs/utils';
export default {
dest: configs.dev.dest,
port: configs.dev.port,
base: configs.website.base,
head: [['link', { rel: 'icon', href: configs.website.icon }]],
title: configs.website.title,
description: configs.website.locales['/en/'].description,
locales: configs.website.locales,
theme: defaultTheme({
logo: configs.website.logo,
repo: configs.github.repo,
docsRepo: configs.github.repo,
docsBranch: configs.github.branch,
docsDir: configs.github.dir,
editLinkPattern: ':repo/edit/:branch/:path',
sidebar: sideBarItems,
sidebarDepth: 2,
locales: {
'/en/': {
navbar: navBarItems['/en/'],
selectLanguageText: 'English (US)',
selectLanguageName: 'English',
editLinkText: 'Edit this page on GitHub',
tip: 'Tips',
warning: 'Notice',
danger: 'Pay Attention',
},
'/zh-cn/': {
navbar: navBarItems['/zh-cn/'],
selectLanguageText: '简体中文 (CN)',
selectLanguageName: '简体中文',
editLinkText: '在 GitHub 上编辑此页',
notFound: ['这里什么都没有', '我们怎么到这来了?', '这是一个 404 页面', '看起来我们进入了错误的链接'],
backToHome: '回到首页',
contributorsText: '贡献者',
lastUpdatedText: '上次更新',
tip: '小提示',
warning: '注意',
danger: '特别注意',
openInNewWindow: '在新窗口中打开',
toggleColorMode: '切换颜色模式'
}
},
}),
extendsMarkdown: (md: markdownit) => {
markdown.injectLinks(md, env.dev ? pageLinkRefs.dev : pageLinkRefs.prod);
},
plugins: [
shikiPlugin({ theme: 'github-dark-dimmed' }),
searchPlugin({
isSearchable: (page) => page.path !== '/',
locales: {
'/en/': { placeholder: 'Search' },
'/zh-cn/': { placeholder: '搜索' }
}
})
]
};

View File

@@ -0,0 +1,146 @@
import { i18n } from './utils';
interface PageLinkRefs {
dev: Record<string, string>[];
prod: Record<string, string>[];
}
const navigationLinks = {
start: [
'/guide/home',
'/guide/quick-start'
],
library: [
'/library/android',
'/library/compose'
],
config: [
'/config/r8-proguard'
],
about: [
'/about/changelog',
'/about/future',
'/about/contacts',
'/about/about'
]
};
export const configs = {
dev: {
dest: 'dist',
port: 9000
},
website: {
base: '/PanguText/',
icon: '/images/logo.png',
logo: '/images/logo.png',
title: 'Pangu Text',
locales: {
'/en/': {
lang: 'en-US',
description: 'A typographic solution for the optimal alignment of CJK characters, English words, and half-width digits'
},
'/zh-cn/': {
lang: 'zh-CN',
description: '一个中日韩 (CJK) 与英文单词、半角数字排版的解决方案'
}
}
},
github: {
repo: 'https://github.com/BetterAndroid/PanguText',
page: 'https://betterandroid.github.io/PanguText',
branch: 'main',
dir: 'docs-source/src'
}
};
export const pageLinkRefs: PageLinkRefs = {
dev: [
{ 'repo://': `${configs.github.repo}/` },
// KDoc URL for local debugging, non-fixed value, adjust according to your own needs.
// You can run ./build-dokka.sh and start the local server in dist/KDoc.
{ 'kdoc://': 'http://localhost:9001/' }
],
prod: [
{ 'repo://': `${configs.github.repo}/` },
{ 'kdoc://': `${configs.github.page}/KDoc/` }
]
};
export const navBarItems = {
'/en/': [{
text: 'Navigation',
children: [{
text: 'Get Started',
children: i18n.array(navigationLinks.start, 'en')
}, {
text: 'Libraries',
children: i18n.array(navigationLinks.library, 'en')
}, {
text: 'Configs',
children: i18n.array(navigationLinks.config, 'en')
}, {
text: 'About',
children: i18n.array(navigationLinks.about, 'en')
}]
}, {
text: 'Contact Us',
link: i18n.string(navigationLinks.about[2], 'en')
}],
'/zh-cn/': [{
text: '导航',
children: [{
text: '入门',
children: i18n.array(navigationLinks.start, 'zh-cn')
}, {
text: '依赖',
children: i18n.array(navigationLinks.library, 'zh-cn')
}, {
text: '配置',
children: i18n.array(navigationLinks.config, 'zh-cn')
}, {
text: '关于',
children: i18n.array(navigationLinks.about, 'zh-cn')
}]
}, {
text: '联系我们',
link: i18n.string(navigationLinks.about[2], 'zh-cn')
}]
};
export const sideBarItems = {
'/en/': [{
text: 'Get Started',
collapsible: true,
children: i18n.array(navigationLinks.start, 'en')
}, {
text: 'Libraries',
collapsible: true,
children: i18n.array(navigationLinks.library, 'en')
}, {
text: 'Configs',
collapsible: true,
children: i18n.array(navigationLinks.config, 'en')
}, {
text: 'About',
collapsible: true,
children: i18n.array(navigationLinks.about, 'en')
}],
'/zh-cn/': [{
text: '入门',
collapsible: true,
children: i18n.array(navigationLinks.start, 'zh-cn')
}, {
text: '依赖',
collapsible: true,
children: i18n.array(navigationLinks.library, 'zh-cn')
}, {
text: '配置',
collapsible: true,
children: i18n.array(navigationLinks.config, 'zh-cn')
}, {
text: '关于',
collapsible: true,
children: i18n.array(navigationLinks.about, 'zh-cn')
}]
};

View File

@@ -0,0 +1,39 @@
export const env = {
dev: process.env.NODE_ENV === 'development'
};
export const i18n = {
space: ' ',
string: (content: string, locale: string) => {
return '/' + locale + content;
},
array: (contents: string[], locale: string) => {
const newContents: string[] = [];
contents.forEach((content) => {
newContents.push(i18n.string(content, locale));
});
return newContents;
}
};
export const markdown = {
injectLinks: (md: markdownit, maps: Record<string, string>[]) => {
const defaultRender = md.renderer.rules.link_open || function (tokens, idx, options, _env, self) {
return self.renderToken(tokens, idx, options);
};
md.renderer.rules.link_open = function (tokens, idx, options, env, self) {
const hrefIndex = tokens[idx].attrIndex('href');
let current = tokens[idx].attrs!![hrefIndex][1];
for (const map of maps) {
for (const [search, replace] of Object.entries(map)) {
if (current.startsWith(search)) {
current = current.replace(search, replace);
tokens[idx].attrs!![hrefIndex][1] = current;
break;
}
}
}
return defaultRender(tokens, idx, options, env, self);
};
}
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

View File

@@ -0,0 +1,179 @@
$primary-color: rgb(99, 159, 112);
$accent-color: rgb(130, 180, 140);
$content-width: 965px;
$scroll-bar-width: 8px;
$scroll-bar-height: 6.5px;
$scroll-bar-border-radius: 50px;
$scroll-bar-track-color-code: rgb(86, 96, 110);
$scroll-bar-thumb-hover-color-code: rgb(121, 135, 155);
:root {
--c-brand: #{$primary-color};
--c-brand-light: #{$accent-color};
--content-width: #{$content-width};
}
code {
padding: 3px 5px 3px 5px;
border-radius: 5px;
}
.badge {
margin-bottom: 5px;
}
.custom-container {
border-radius: 5px;
}
.sidebar-item {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.language-text {
::-webkit-scrollbar-track {
background: #{$scroll-bar-track-color-code};
border-radius: #{$scroll-bar-border-radius};
}
::-webkit-scrollbar-thumb:hover {
background: #{$scroll-bar-thumb-hover-color-code};
}
}
.language-kotlin {
::-webkit-scrollbar-track {
background: #{$scroll-bar-track-color-code};
border-radius: #{$scroll-bar-border-radius};
}
::-webkit-scrollbar-thumb:hover {
background: #{$scroll-bar-thumb-hover-color-code};
}
}
.language-java {
::-webkit-scrollbar-track {
background: #{$scroll-bar-track-color-code};
border-radius: #{$scroll-bar-border-radius};
}
::-webkit-scrollbar-thumb:hover {
background: #{$scroll-bar-thumb-hover-color-code};
}
}
.language-groovy {
::-webkit-scrollbar-track {
background: #{$scroll-bar-track-color-code};
border-radius: #{$scroll-bar-border-radius};
}
::-webkit-scrollbar-thumb:hover {
background: #{$scroll-bar-thumb-hover-color-code};
}
}
.language-xml {
::-webkit-scrollbar-track {
background: #{$scroll-bar-track-color-code};
border-radius: #{$scroll-bar-border-radius};
}
::-webkit-scrollbar-thumb:hover {
background: #{$scroll-bar-thumb-hover-color-code};
}
}
.hidden-anchor-page {
h6 {
color: transparent;
margin-bottom: -35px;
padding-top: 50px;
}
}
.code-page {
h1 {
font-size: 24pt;
}
h2 {
font-size: 18pt;
}
h3 {
font-size: 15pt;
}
h4 {
font-size: 12pt;
}
h5 {
font-size: 9.6pt;
}
h6 {
font-size: 8.4pt;
}
.symbol {
color: rgb(142, 155, 168);
}
.deprecated {
color: rgb(142, 155, 168);
text-decoration: line-through;
}
}
html {
scroll-behavior: smooth;
::-webkit-scrollbar {
width: #{$scroll-bar-width};
height: #{$scroll-bar-height};
}
::-webkit-scrollbar-track {
background: rgb(234, 236, 239);
}
::-webkit-scrollbar-thumb {
background: rgb(189, 189, 189);
border-radius: #{$scroll-bar-border-radius};
}
::-webkit-scrollbar-thumb:hover {
background: rgb(133, 133, 133);
border-radius: #{$scroll-bar-border-radius};
}
}
html.dark {
--c-brand: #{$primary-color};
--c-brand-light: #{$accent-color};
--content-width: #{$content-width};
::-webkit-scrollbar {
width: #{$scroll-bar-width};
height: #{$scroll-bar-height};
}
::-webkit-scrollbar-track {
background: rgb(41, 46, 53);
}
::-webkit-scrollbar-thumb {
background: rgb(65, 72, 83);
border-radius: #{$scroll-bar-border-radius};
}
::-webkit-scrollbar-thumb:hover {
background: rgb(56, 62, 72);
border-radius: #{$scroll-bar-border-radius};
}
}

View File

@@ -0,0 +1,27 @@
# About This Document
> This document is powered by [VuePress](https://v2.vuepress.vuejs.org/en).
## License
[Apache-2.0](https://github.com/HighCapable/YukiReflection/blob/master/LICENSE)
```:no-line-numbers
Apache License Version 2.0
Copyright (C) 2019 HighCapable
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
```
Copyright © 2019 HighCapable

View File

@@ -0,0 +1,27 @@
# Changelog
> The version update history of `PanguText` is recorded here.
::: danger
We will only maintain the latest API version, if you are using an outdate API version, you voluntarily renounce any possibility of maintenance.
:::
::: warning
To avoid translation time consumption, Changelog will use **Google Translation** from **Chinese** to **English**, please refer to the original text for actual reference.
Time zone of version release date: **UTC+8**
:::
## pangutext-android
### 1.0.0 | 2025.02.10 &ensp;<Badge type="tip" text="latest" vertical="middle" />
- The first version is submitted to Maven
## pangutext-compose
Not yet released.

View File

@@ -0,0 +1,16 @@
# Contact Us
> If you have any questions in use, or have any constructive suggestions, you can contact us.
Join our developers group.
- [Click to join Telegram group](https://t.me/BetterAndroid)
- [Click to join Telegram group (Developer)](https://t.me/HighCapable_Dev)
Find me on **Twitter** [@fankesyooni](https://twitter.com/fankesyooni).
## Help with Maintenance
Thank you for choosing and using `PanguText`.
If you have code-related suggestions and requests, you can submit a Pull Request on GitHub.

View File

@@ -0,0 +1,15 @@
# Looking for Future
> The future is bright and uncertain, let us look forward to the future development space of `PanguText`.
## Future Plans
> Features that `PanguText` may add later are included here.
### Limitations of SpannableString
`PanguText`'s main functionality on the Android platform currently comes from `SpannableString`, which has not yet fully resolved the issues of handling complex text styles and performance overhead.
### Jetpack Compose Plan
`PanguText` will support Jetpack Compose in the future and plans to use `AnnotatedString` as the main text processing method to minimize intrusion into the underlying layer.

View File

@@ -0,0 +1,6 @@
# R8 & Proguard Obfuscate
> In most scenarios, the app packages can be compressed through obfuscation,
> here is an introduction to how to configure obfuscation rules.
`PanguText` does not require any additional obfuscation rules.

View File

@@ -0,0 +1,70 @@
# Introduce
> `PanguText` is a solution for CJK (Chinese, Japanese, Korean) and English word, half-width number spacing.
## Background
This project was created because, until now, there hasnt been a public solution to perfectly address the typography issues between Chinese, Japanese, Korean, and English.
Typically, when mixing CJK (i.e. Chinese, Japanese, Korean) with English, aesthetic issues can arise—a historical legacy stemming from the differences in writing conventions between full-width and half-width characters. Although the W3C has now established CJK typography guidelines, only a few individuals or companies willing to adhere to these standards have adopted this approach.
Currently, the known vendor solutions are as follows:
- Apple platforms (iOS, iPadOS, macOS, tvOS, watchOS) text typography solutions
- Xiaomis (HyperOS) text typography optimization
- OrginOSs font-based text typography optimization
However, these solutions are closed and cannot be implemented on other platforms.
We aim to provide an open-source solution adaptable to various scenarios, featuring low intrusiveness and easy integration, allowing more developers to effectively address text typography issues.
The primary inspiration for this project comes from [pangu.js](https://github.com/vinta/pangu.js), which offers a set of regular expressions for CJK typography.
We have optimized these solutions to format text across platforms without inserting extra space characters. We extend this approach further to explore additional possibilities.
Heartfelt thanks to the original developer of **pangu.js** for providing the foundational solution.
## Effects
As you can see, the typography scheme of `PanguText` does not work by simply inserting spaces between CJK characters and English words.
Instead, it leverages each platform's native handling to automatically add whitespace between these characters, ensuring minimal intrusion.
> Before Applying (Top) vs. After Applying (Bottom)
<img src="/images/demo_01.png" width="300" />
> Dynamic Application
<img src="/images/demo_02.gif" width="480" />
`PanguText` supports dynamic application, which means it can add whitespace gaps to each character on-the-fly as you input text.
::: tip Developer's Perspective
I personally do not recommend manually inserting spaces between CJK and English characters for typographic refinement if your software or system natively supports enhanced typographic formatting.
The spacing can vary across fonts, which may lead to formatting issues and the insertion of undesired space characters.
In certain contexts, such as URLs, filenames, or hashtags containing “#”, these spaces are not acceptable.
However, in special scenarios—for example, within code comments or documentation—it can be beneficial to add spaces, as these areas typically do not employ automated formatting tools.
Another point to consider is the use of different punctuation marks in different languages.
Avoid mixing full-width and half-width punctuation marks.
If you must use half-width punctuation marks to annotate full-width text, ensure that the half-width marks are followed by a space to complete the character space (the same applies to English).
:::
## Language Requirement
It is recommended to use Kotlin as the preferred development language.
This project is entirely written in Kotlin and is compatible with Java in some parts, but it may not be fully compatible.
All demo & sample codes in the document will be described using Kotlin, if you dont know how to use Kotlin at all, you may not get the best experience.
## Contribution
The maintenance of this project is inseparable from the support and contributions of all developers.
This project is currently in its early stages, and there may still be some problems or lack of functions you need.
If possible, feel free to submit a PR to contribute features you think are needed to this project or goto [GitHub Issues](repo://issues)
to make suggestions to us.

View File

@@ -0,0 +1,86 @@
# Quick Start
> Integrate `PanguText` into your project.
## Project Requirements
The project needs to be created using `Android Studio` or `IntelliJ IDEA` and be of type Android or Kotlin Multiplatform
project and have integrated Kotlin environment dependencies.
- Android Studio (It is recommended to get the latest version [from here](https://developer.android.com/studio))
- IntelliJ IDEA (It is recommended to get the latest version [from here](https://www.jetbrains.com/idea))
- Kotlin 1.9.0+, Gradle 8+, Java 17+, Android Gradle Plugin 8+
### Configure Repositories
The dependencies of `PanguText` are published in **Maven Central** and our public repository,
you can use the following method to configure repositories.
We recommend using Kotlin DSL as the Gradle build script language and [SweetDependency](https://github.com/HighCapable/SweetDependency)
to manage dependencies.
#### SweetDependency (Recommended)
Configure repositories in your project's `SweetDependency` configuration file.
```yaml
repositories:
google:
maven-central:
# (Optional) You can add this URL to use our public repository
# When Sonatype-OSS fails and cannot publish dependencies, this repository is added as a backup
# For details, please visit: https://github.com/HighCapable/maven-repository
highcapable-maven-releases:
url: https://raw.githubusercontent.com/HighCapable/maven-repository/main/repository/releases
```
#### Traditional Method
Configure repositories in your project `build.gradle.kts`.
```kotlin
repositories {
google()
mavenCentral()
// (Optional) You can add this URL to use our public repository
// When Sonatype-OSS fails and cannot publish dependencies, this repository is added as a backup
// For details, please visit: https://github.com/HighCapable/maven-repository
maven("https://raw.githubusercontent.com/HighCapable/maven-repository/main/repository/releases")
}
```
### Configure Java Version
Modify the Java version of Kotlin in your project `build.gradle.kts` to 17 or above.
> Kotlin DSL
```kt
android {
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = "17"
}
}
```
## Functional Overview
The project is divided into multiple modules: Android platform and Jetpack Compose (multiplatform). You can choose the module you wish to include as a dependency in your project.
Click the corresponding module below to view detailed feature descriptions.
- [Android](../library/android.md)
- [Jetpack Compose](../library/compose.md)
## Demo
You can find some examples below. Check out the corresponding demo projects to get a better understanding of how these features work and quickly select the functionality you need.
- [Android](repo://tree/main/demo-android)
- [Jetpack Compose (Coming soon)](repo://tree/main/demo-compose)

View File

@@ -0,0 +1,13 @@
---
home: true
title: Home
heroImage: /images/logo.png
actions:
- text: Get Started
link: /en/guide/home
type: primary
- text: Changelog
link: /en/about/changelog
type: secondary
footer: Apache-2.0 License | Copyright (C) 2019 HighCapable
---

View File

@@ -0,0 +1,367 @@
# Android
![Maven Central](https://img.shields.io/maven-central/v/com.highcapable.pangutext/pangutext-android?logo=apachemaven&logoColor=orange)
<span style="margin-left: 5px"/>
![Maven metadata URL](https://img.shields.io/maven-metadata/v?metadataUrl=https%3A%2F%2Fraw.githubusercontent.com%2FHighCapable%2Fmaven-repository%2Frefs%2Fheads%2Fmain%2Frepository%2Freleases%2Fcom%2Fhighcapable%2Fpangutext%2Fpangutext-android%2Fmaven-metadata.xml&logo=apachemaven&logoColor=orange&label=highcapable-maven-releases)
<span style="margin-left: 5px"/>
![Android Min SDK](https://img.shields.io/badge/Min%20SDK-21-orange?logo=android)
This is the core dependency for the Android platform. When using `PanguText` on Android, you need to include this module.
## Configure Dependency
You can add this module to your project using the following method.
### SweetDependency (Recommended)
Add dependency in your project's `SweetDependency` configuration file.
```yaml
libraries:
com.highcapable.pangutext:
pangutext-android:
version: +
```
Configure dependency in your project `build.gradle.kts`.
```kotlin
implementation(com.highcapable.pangutext.pangutext.android)
```
### Traditional Method
Configure dependency in your project `build.gradle.kts`.
```kotlin
implementation("com.highcapable.pangutext:pangutext-android:<version>")
```
Please change `<version>` to the version displayed at the top of this document.
## Function Introduction
You can view the KDoc [click here](kdoc://pangutext-android).
### Implementation Principle
`PanguText` provides two methods for text formatting on the Android platform: `SpannableString` (does not alter the original text length) and direct insertion of whitespace characters (alters the original text length).
The first method, `SpannableString`, adds a `Span` with spacing to the character before the one that needs spacing, changing the text style without altering the string content. The rendering is done by the `TextView` layer (or manually using `TextPaint` based on `Spanned` for layout styling), achieving non-intrusive text styling.
This method also supports processing already styled text (`Spanned`), such as text created via `Html.fromHtml`.
**However, it is currently experimental and may still have unexpected style errors**. You can refer to the [Personalized Configuration](#personalized-configuration) section below to disable it.
The dynamic application (injection) feature mainly targets the input state of `EditText`. It sets a custom `TextWatcher` for `EditText` to monitor input changes and formats the text from `afterTextChanged`.
The second method directly inserts whitespace characters after the characters that need spacing. This method alters the original text length and content but does not rely on the `TextView` layer for rendering. It uses `TextPaint` to draw the text directly, suitable for all scenarios, **but does not support dynamic application (injection)**.
::: warning Unresolved Issues
`PanguText` may conflict with Material components like `TextInputEditText`, `MaterialAutoCompleteTextView`, and `TextInputLayout` when using `setHint`, as `TextView` does not account for `Span` during measurement. This issue is particularly noticeable in single-line text, and there is no solution yet. Use these components cautiously.
Due to the above issue, calculating the width of a `TextView` with `PanguText` style using the `View.measure` method may also result in errors.
`PanguText` currently cannot handle continuous characters like underlines or strikethroughs in `Spanned` text, as the lines will break after adding spacing. It may also cause style errors or fail to apply styles correctly to some special characters. For stability, avoid enabling `PanguText` for very complex rich text or refer to the [Personalized Configuration](#personalized-configuration) section to set `excludePatterns`.
:::
### Integrate into Existing Projects
Integrating `PanguText` into your current project is very easy. You don't need to change much code. Choose your preferred method below to complete the integration.
#### Inject to LayoutInflater
`PanguText` supports direct injection of `LayoutInflater.Factory2` or creating a `LayoutInflater.Factory2` instance for the current `Activity` to take over the entire view. This is the recommended integration method, as it allows for non-intrusive and quick integration without modifying any existing layouts.
> The following example
```kotlin
class MainActivity : AppCompatActivity() {
val binding by lazy { ActivityMainBinding.inflate(layoutInflater) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Inject here.
PanguTextFactory2.inject(this)
setContentView(binding.root)
}
}
```
::: tip
Since `LayoutInflater.Factory2` is taken over, recycled layouts like `ListView` and `RecyclerView` can also be correctly taken over.
After injecting the `LayoutInflater` instance in the `Activity`, the following instances attached to the current `Context` will automatically take effect:
- `Fragment`
- `Dialog`
- `PopupWindow`
- `Toast` (foreground only in higher system versions)
Layouts based on `RemoteView` will not take effect because they are remote objects and do not use the current `Context`'s `LayoutInflater` for layout loading.
:::
If you are using [ui-component → AppBindingActivity](https://betterandroid.github.io/BetterAndroid/KDoc/ui-component/ui-component/com.highcapable.betterandroid.ui.component.activity/-app-binding-activity) in `BetterAndroid`, you need to slightly modify the current code.
> The following example
```kotlin
class MainActivity : AppBindingActivity<ActivityMainBinding>() {
override fun onPrepareContentView(savedInstanceState: Bundle?): LayoutInflater {
val inflater = super.onPrepareContentView(savedInstanceState)
// Inject here.
PanguTextFactory2.inject(inflater)
return inflater
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Your code here.
}
}
```
If your application does not use `AppCompatActivity` or `ViewBinding`, don't worry, you can still use the original method.
> The following example
```kotlin
class MainActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Inject here.
PanguTextFactory2.inject(this)
setContentView(R.layout.activity_main)
}
}
```
::: tip
`PanguTextFactory2` can be used not only with `Activity` but also injected into any existing `LayoutInflater` instance.
However, please inject before the `LayoutInflater` instance is used to load the layout, otherwise it will not take effect.
:::
#### Manual Injection or Text Formatting
`PanguText` also supports manual injection, allowing you to inject it into the desired `TextView` or `EditText`.
> The following example
```kotlin
// Assume this is your TextView.
val textView: TextView
// Assume this is your EditText.
val editText: EditText
// Inject into existing text.
textView.injectPanguText()
editText.injectPanguText()
// Optionally choose whether to inject Hint (default is true).
textView.injectPanguText(injectHint = false)
editText.injectPanguText(injectHint = false)
// Dynamic injection, re-calling setText will automatically take effect.
textView.injectRealTimePanguText()
// Dynamic injection mainly targets the input state of EditText.
editText.injectRealTimePanguText()
// Optionally choose whether to inject Hint (default is true).
textView.injectRealTimePanguText(injectHint = false)
editText.injectRealTimePanguText(injectHint = false)
```
`PanguText` also extends the `setText` method of `TextView`, allowing you to directly set text with `PanguText` style.
> The following example
```kotlin
// Assume this is your TextView.
val textView: TextView
// Set text with PanguText style.
textView.setTextWithPangu("Xiaoming今年16岁")
// Set Hint with PanguText style.
textView.setHintWithPangu("输入Xiaoming的年龄")
```
You can also use the `PanguText.format` method to directly format text.
> The following example
```kotlin
// Assume this is your TextView.
val textView: TextView
// Format text using SpannableString method.
// Requires passing the current TextView's Resources and text size.
// If the input text is already Spannable,
// it will return the original object without creating a new SpannableString.
val text = PanguText.format(textView.resources, textView.textSize, "Xiaoming今年16岁")
// Set text.
textView.text = text
// Directly format text using whitespace characters for insertion.
// This method adds extra whitespace characters "" (HSP) to the text.
// The result below will output the string "Xiaoming今年16岁".
// You can also customize the whitespace character at the end of the method.
val text = PanguText.format("Xiaoming今年16岁")
// Set text.
textView.text = text
```
::: tip
The `injectPanguText`, `injectRealTimePanguText`, `setTextWithPangu`, `setHintWithPangu`, and `PanguText.format` methods support the `config` parameter.
You can refer to the [Personalized Configuration](#personalized-configuration) section below.
:::
#### Custom View
`PanguText` can also be used with custom `View`. You can extend your `View` to `AppCompatTextView` and override the `setText` method.
> The following example
```kotlin
class MyTextView(context: Context, attrs: AttributeSet? = null) : AppCompatTextView(context, attrs) {
override fun setText(text: CharSequence?, type: BufferType?) {
// Manually inject here.
val panguText = text?.let { PanguText.format(resources, textSize, it) }
super.setText(panguText, type)
}
}
```
::: warning
After injecting `PanguText` into `TextView`, if you use `android:singleLine="true"` in XML layout or `TextView.setSingleLine(true)` in code along with `android:ellipsize="..."`,
this method of setting single-line text may cause unresolvable `OBJ` characters (truncated by ellipsis) to appear when the text exceeds the screen width, because `TextView` does not account for `Span` during measurement, leading to incorrect text width calculation.
The solution is to use `android:maxLines="1"` in XML layout or `TextView.setMaxLines(1)` in code instead.
> The following example
```xml
<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="这是一段很长很长长长长长长长长长长长长还有English混入的的文本"
android:maxLines="1"
android:ellipsize="end" />
```
:::
### Personalized Configuration
`PanguText` supports personalized configuration. You can use the global static instance `PanguText.globalConfig` to get the global configuration or configure it individually.
> The following example
```kotlin
// Get global configuration.
val config = PanguText.globalConfig
// Enable or disable the feature.
config.isEnabled = true
// Process Spanned text.
// Processing Spanned text is enabled by default, but this feature is experimental.
// If issues occur, you can disable it. When disabled, Spanned text will return the original text.
config.isProcessedSpanned = true
// Set patterns to exclude during formatting using regular expressions.
// For example, exclude all URLs.
config.excludePatterns.add("https?://\\S+".toRegex())
// For example, exclude emoji placeholders like "[doge]",
// if you use [ImageSpan] to display emoji images, you can choose to exclude these placeholders.
config.excludePatterns.add("\\[.*?]".toRegex())
// Set the spacing ratio for CJK characters.
// This determines the final layout effect.
// It is recommended to keep the default ratio and adjust it according to personal preference.
config.cjkSpacingRatio = 7f
```
::: warning
If you integrated using the [Inject to LayoutInflater](#inject-to-layoutinflater) method, configure `PanguText.globalConfig` before executing `PanguTextFactory2.inject(...)`, otherwise the configuration will not take effect.
:::
You can also pass the `config` parameter for personalized configuration when manually injecting or formatting text.
> The following example
```kotlin
// Assume this is your TextView.
val textView: TextView
// Create a new configuration.
// You can set [copyFromGlobal] to false to not copy from the global configuration.
val config = PanguTextConfig(copyFromGlobal = false) {
excludePatterns.add("https?://\\S+".toRegex())
excludePatterns.add("\\[.*?]".toRegex())
cjkSpacingRatio = 7f
}
// You can also copy and create a new configuration from any configuration.
val config2 = config.copy {
excludePatterns.clear()
excludePatterns.add("https?://\\S+".toRegex())
excludePatterns.add("\\[.*?]".toRegex())
cjkSpacingRatio = 7f
}
// Manually inject and configure.
textView.injectPanguText(config = config2)
```
If you integrated using the [Inject to LayoutInflater](#inject-to-layoutinflater) method, you can use the following attributes in the XML layout declaration of `TextView`, `EditText`, or their subclasses for personalized configuration.
- `panguText_enabled` corresponds to `PanguTextConfig.isEnabled`
- `panguText_processedSpanned` corresponds to `PanguTextConfig.isProcessedSpanned`
- `panguText_excludePatterns` corresponds to `PanguTextConfig.excludePatterns`, string array, multiple patterns separated by `|@|`
- `panguText_cjkSpacingRatio` corresponds to `PanguTextConfig.cjkSpacingRatio`
> The following example
```xml
<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Xiaoming今年16岁"
app:panguText_enabled="true"
app:panguText_processedSpanned="true"
app:panguText_excludePatterns="https?://\\S+;\\[.*?]|@|\\[.*?]"
app:panguText_cjkSpacingRatio="7.0" />
```
::: warning
Due to issues with Android Studio, the above attributes may not have auto-completion hints. Please complete them manually.
Don't forget to add the declaration `xmlns:app="http://schemas.android.com/apk/res-auto"`.
:::
In custom `View`, you can extend your `View` to implement the `PanguTextView` interface to achieve the same functionality.
> The following example
```kotlin
class MyTextView(context: Context, attrs: AttributeSet? = null) : AppCompatTextView(context, attrs),
PanguTextView {
override fun configurePanguText(config: PanguTextConfig) {
// Configure your [PanguTextConfig].
}
}
```
::: warning
The `PanguTextView` interface takes precedence over attributes used directly in the XML layout. If you use both methods for configuration, the `PanguTextView` interface configuration will override the XML layout configuration.
Individual configurations will override global configurations, and options not configured will follow the global configuration.
:::

View File

@@ -0,0 +1,9 @@
# Jetpack Compose
![Maven Central](https://img.shields.io/maven-central/v/com.highcapable.pangutext/pangutext-compose?logo=apachemaven&logoColor=orange)
<span style="margin-left: 5px"/>
![Maven metadata URL](https://img.shields.io/maven-metadata/v?metadataUrl=https%3A%2F%2Fraw.githubusercontent.com%2FHighCapable%2Fmaven-repository%2Frefs%2Fheads%2Fmain%2Frepository%2Freleases%2Fcom%2Fhighcapable%2Fpangutext%2Fpangutext-compose%2Fmaven-metadata.xml&logo=apachemaven&logoColor=orange&label=highcapable-maven-releases)
This is the core dependency for Jetpack Compose (multiplatform). When using `PanguText` in Jetpack Compose, you need to include this module.
This module is currently under development and will be gradually improved in the future.

17
docs-source/src/index.md Normal file
View File

@@ -0,0 +1,17 @@
---
home: true
navbar: false
sidebar: false
title: null
heroAlt: null
heroText: null
tagline: Select a language
actions:
- text: English
link: /en/
type: secondary
- text: 简体中文
link: /zh-cn/
type: secondary
footer: Apache-2.0 License | Copyright (C) 2019 HighCapable
---

View File

@@ -0,0 +1,27 @@
# 关于此文档
> 此文档由 [VuePress](https://v2.vuepress.vuejs.org/zh) 强力驱动。
## 许可证
[Apache-2.0](https://github.com/HighCapable/YukiReflection/blob/master/LICENSE)
```:no-line-numbers
Apache License Version 2.0
Copyright (C) 2019 HighCapable
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
```
版权所有 © 2019 HighCapable

View File

@@ -0,0 +1,19 @@
# 更新日志
> 这里记录了 `PanguText` 的版本更新历史。
::: danger
我们只会对最新的 API 版本进行维护,若你正在使用过时的 API 版本则代表你自愿放弃一切维护的可能性。
:::
## pangutext-android
### 1.0.0 | 2025.02.10 &ensp;<Badge type="tip" text="最新" vertical="middle" />
- 首个版本提交至 Maven
## pangutext-compose
暂未发布。

View File

@@ -0,0 +1,15 @@
# 联系我们
> 如在使用中有任何问题,或有任何建设性的建议,都可以联系我们。
加入我们的开发者群组。
- [点击加入 Telegram 群组](https://t.me/BetterAndroid)
- [点击加入 Telegram 群组 (开发者)](https://t.me/HighCapable_Dev)
- [点击加入 QQ 群 (开发者)](https://qm.qq.com/cgi-bin/qm/qr?k=Pnsc5RY6N2mBKFjOLPiYldbAbprAU3V7&jump_from=webapi&authKey=X5EsOVzLXt1dRunge8ryTxDRrh9/IiW1Pua75eDLh9RE3KXE+bwXIYF5cWri/9lf)
**酷安** 找到我 [@星夜不荟](http://www.coolapk.com/u/876977)。
## 助力维护
感谢您选择并使用 `PanguText`,如有代码相关的建议和请求,可在 GitHub 提交 Pull Request。

View File

@@ -0,0 +1,15 @@
# 展望未来
> 未来是美好的,也是不确定的,让我们共同期待 `PanguText` 在未来的发展空间。
## 未来的计划
> 这里收录了 `PanguText` 可能会在后期添加的功能。
### SpannableString 的局限性
`PanguText` 目前在 Android 平台上的主要功能来自 `SpannableString`,目前尚未完全解决处理复杂的文本样式以及性能开销问题。
### Jetpack Compose 计划
`PanguText` 未来将会支持 Jetpack Compose并计划采用 `AnnotatedString` 作为主要的文本处理方式以实现对底层的最小化侵入。

View File

@@ -0,0 +1,5 @@
# R8 与 Proguard 混淆
> 大部分场景下应用程序安装包可通过混淆压缩体积,这里介绍了混淆规则的配置方法。
`PanguText` 不需要额外配置混淆规则。

View File

@@ -0,0 +1,55 @@
# 介绍
> `PanguText` 是一个中日韩 (CJK) 与英文单词、半角数字排版的解决方案。
## 背景
这个项目的起因是因为直到目前为止还没有一套公开的方案能够完美解决中文、日文、韩文与英文之间的排版问题,
正常情况下我们将 CJK (即中日韩) 与英文混排的时候,都会涉及到美观性问题,这算是一个历史遗留问题,全角文字与半角文字之间的书写规范不一样。虽然现在 W3C 规定了 CJK 排版规范,
但是还是仅有部分愿意遵守排版要求的个人或企业选择了这种方案。
目前已知的厂商解决方案如下
- Apple 全系 (iOS、iPadOS、macOS、tvOS、watchOS) 文本排版解决方案
- 小米 (HyperOS) 文本排版优化
- OrginOS 基于字体的文本排版优化
但是这些方案都是封闭的,无法在其他平台上使用,因此我们希望能够提供一套开源的解决方案,能够适应各种场景、侵入性低且更容易集成,让更多的开发者能够使用这个方案来解决文本排版问题。
本项目得以进行的主要来源为 [pangu.js](https://github.com/vinta/pangu.js),它提供了一套 CJK 排版的正则,我们对其加以优化,实现各个平台不需要插入空格字符即可格式化文本排版的效果,
衷心感谢这个项目的开发者提供的方案,我们在这个方案上加以扩展,提供了更多解决方案的可能性。
## 效果
如你所见,`PanguText` 的排版方案并不是向 CJK 与英文单词之间插入空格来完成,而是使用每个平台对应的处理方案自动在这些字符之间添加空白间距来达到排版效果以达到最低的侵入性。
> 应用前 (上)、应用后 (下)
<img src="/images/demo_01.png" width="300" />
> 动态应用
<img src="/images/demo_02.gif" width="480" />
`PanguText` 支持动态应用,它允许你在输入文本的同时动态为每个字符添加空白间距。
::: tip 开发者的观点
我个人依然不提倡手动为 CJK 和英文字符之间添加空格来达到排版美化效果 (如果软件、系统本身支持这种排版美化方式)
因为空格在不同的字体中的间距也是不一样的,这会造成排版效果出现问题,也会被加入本不应该出现的空格字符,在某些场景下,例如网址、文件名或者带有 “#”
的话题标签,不允许出现这些空格。但是,在一些特殊场景,例如代码的注释中,涉及到代码的说明文档,建议加入空格,因为这些范围内可能不会有排版格式化工具。
还有一点就是,在不同的语言中使用不同的标点符号,切忌全角和半角标点符号混用,如果一定要使用半角标点符号来标记全角文字,在句子未结束时将半角符号向后推进一个空格补全字符空间 (英文也是如此)。
:::
## 语言要求
推荐使用 Kotlin 作为首选开发语言,本项目完全使用 Kotlin 编写,在部分内容上对 Java 做了兼容处理,但也许无法做到完全兼容。
文档全部的 Demo 示例代码都将使用 Kotlin 进行描述,如果你完全不会使用 Kotlin那么你将有可能无法获得最佳使用体验。
## 功能贡献
本项目的维护离不开各位开发者的支持和贡献,目前这个项目处于初期阶段,可能依然存在一些问题或者缺少你需要的功能,
如果可能,欢迎提交 PR 为此项目贡献你认为需要的功能或前往 [GitHub Issues](repo://issues) 向我们提出建议。

View File

@@ -0,0 +1,83 @@
# 快速开始
> 集成 `PanguText` 到你的项目中。
## 项目要求
项目需要使用 `Android Studio``IntelliJ IDEA` 创建且类型为 Android 或 Kotlin Multiplatform 项目并已集成 Kotlin 环境依赖。
- Android Studio (建议 [从这里](https://developer.android.com/studio) 获取最新版本)
- IntelliJ IDEA (建议 [从这里](https://www.jetbrains.com/idea) 获取最新版本)
- Kotlin 1.9.0+、Gradle 8+、Java 17+、Android Gradle Plugin 8+
### 配置存储库
`PanguText` 的依赖发布在 **Maven Central** 和我们的公共存储库中,你可以使用如下方式配置存储库。
我们推荐使用 Kotlin DSL 作为 Gradle 构建脚本语言并推荐使用 [SweetDependency](https://github.com/HighCapable/SweetDependency) 来管理依赖。
#### SweetDependency (推荐)
在你的项目 `SweetDependency` 配置文件中配置存储库。
```yaml
repositories:
google:
maven-central:
# (可选) 你可以添加此 URL 以使用我们的公共存储库
# 当 Sonatype-OSS 发生故障无法发布依赖时,此存储库作为备选进行添加
# 详情请前往https://github.com/HighCapable/maven-repository
highcapable-maven-releases:
# 中国大陆用户请将下方的 "raw.githubusercontent.com" 修改为 "raw.gitmirror.com"
url: https://raw.githubusercontent.com/HighCapable/maven-repository/main/repository/releases
```
#### 传统方式
在你的项目 `build.gradle.kts` 中配置存储库。
```kotlin
repositories {
google()
mavenCentral()
// (可选) 你可以添加此 URL 以使用我们的公共存储库
// 当 Sonatype-OSS 发生故障无法发布依赖时,此存储库作为备选进行添加
// 详情请前往https://github.com/HighCapable/maven-repository
// 中国大陆用户请将下方的 "raw.githubusercontent.com" 修改为 "raw.gitmirror.com"
maven("https://raw.githubusercontent.com/HighCapable/maven-repository/main/repository/releases")
}
```
### 配置 Java 版本
在你的项目 `build.gradle.kts` 中修改 Kotlin 的 Java 版本为 17 及以上。
```kt
android {
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = "17"
}
}
```
## 功能一览
整个项目分为多个模块Android 平台与 Jetpack Compose (多平台),你可以选择你希望引入的模块作为依赖应用到你的项目中。
你可以点击下方对应的模块前往查看详细的功能介绍。
- [Android](../library/android.md)
- [Jetpack Compose](../library/compose.md)
## Demo
你可以在下方找到一些示例,查看对应的演示项目来更好地了解这些功能的运作方式,快速地挑选出你需要的功能。
- [Android](repo://tree/main/demo-android)
- [Jetpack Compose (敬请期待)](repo://tree/main/demo-compose)

View File

@@ -0,0 +1,13 @@
---
home: true
title: 首页
heroImage: /images/logo.png
actions:
- text: 快速上手
link: /zh-cn/guide/home
type: primary
- text: 更新日志
link: /zh-cn/about/changelog
type: secondary
footer: Apache-2.0 License | Copyright (C) 2019 HighCapable
---

View File

@@ -0,0 +1,365 @@
# Android
![Maven Central](https://img.shields.io/maven-central/v/com.highcapable.pangutext/pangutext-android?logo=apachemaven&logoColor=orange)
<span style="margin-left: 5px"/>
![Maven metadata URL](https://img.shields.io/maven-metadata/v?metadataUrl=https%3A%2F%2Fraw.githubusercontent.com%2FHighCapable%2Fmaven-repository%2Frefs%2Fheads%2Fmain%2Frepository%2Freleases%2Fcom%2Fhighcapable%2Fpangutext%2Fpangutext-android%2Fmaven-metadata.xml&logo=apachemaven&logoColor=orange&label=highcapable-maven-releases)
<span style="margin-left: 5px"/>
![Android Min SDK](https://img.shields.io/badge/Min%20SDK-21-orange?logo=android)
这是 Android 平台的核心依赖,在 Android 平台上使用 `PanguText` 时,你需要引入此模块。
## 配置依赖
你可以使用如下方式将此模块添加到你的项目中。
### SweetDependency (推荐)
在你的项目 `SweetDependency` 配置文件中添加依赖。
```yaml
libraries:
com.highcapable.pangutext:
pangutext-android:
version: +
```
在你的项目 `build.gradle.kts` 中配置依赖。
```kotlin
implementation(com.highcapable.pangutext.pangutext.android)
```
### 传统方式
在你的项目 `build.gradle.kts` 中配置依赖。
```kotlin
implementation("com.highcapable.pangutext:pangutext-android:<version>")
```
请将 `<version>` 修改为此文档顶部显示的版本。
## 功能介绍
你可以 [点击这里](kdoc://pangutext-android) 查看 KDoc。
### 实现原理
`PanguText` 在 Android 平台有两种方案对文本进行格式化,一种为 `SpannableString` (不破坏原始文本长度),另一种则是直接插入空白字符 (破坏原始文本长度)。
第一种方案为 `SpannableString`,它会在需要增加间距的字符的前一个字符后增加应用了间距的 `Span` 来实现文本在样式上的改变,而不实际改变字符串的内容,最后交由 `TextView` 层完成渲染 (或手动使用 `TextPaint` 基于 `Spanned` 做布局样式处理),实现无侵入式为文本设置样式。
第一种方案同样支持直接处理已经应用了样式的文本 (`Spanned`),例如通过 `Html.fromHtml` 创建的文本,**但是目前尚处于实验性阶段,可能仍然会出现非预期样式错误问题**
你可以参考下方的 [个性化配置](#个性化配置) 选择禁用它。
动态应用 (注入) 功能主要针对 `EditText` 的输入状态,它会为 `EditText` 设置一个自定义的 `TextWatcher` 来监听输入状态,当输入状态发生变化时,从 `afterTextChanged` 中获取 `Editable` 并进行格式化。
第二种方案则是直接插入空白字符,它会直接在需要增加间距的字符后插入空白字符,这种方案会破坏原始文本的长度并且会改变文本内容自身,
但是可以不依赖于 `TextView` 层完成渲染,直接使用 `TextPaint` 绘制文本即可,适用于所有场景,**但不支持动态应用 (注入)**。
::: warning 尚未解决的问题
`PanguText` 可能会与 Material 组件 `TextInputEditText``MaterialAutoCompleteTextView``TextInputLayout` 结合时在 `setHint` 效果上产生冲突,
因为 `TextView` 不会在测量时计算文本中的 `Span`,在单行文本中此类问题尤为明显,暂时还没有解决方案,请谨慎配合此类组件使用。
受制于上述问题,通过 `View.measure` 方法计算包含了 `PanguText` 风格的 `TextView` 宽度时也可能会出现错误。
`PanguText` 目前不能处理 `Spanned` 文本中的下划线、删除线这种连续的字符,添加空白间距后线条会中断,
并且它可能会在一些特殊字符上发生样式错误或样式没有被正确应用,为了稳定性考虑请尽量不要对非常复杂的富文本启用 `PanguText` 或参考下方的 [个性化配置](#个性化配置) 设置 `excludePatterns`
:::
### 集成到现有项目
`PanguText` 集成到你的当前项目中非常容易,你不需要改动过多代码,挑选以下你喜欢的方案进行,即可完成集成。
#### 注入布局装载器 (LayoutInflater)
`PanguText` 支持直接注入 `LayoutInflater.Factory2` 或为当前 `Activity` 创建 `LayoutInflater.Factory2` 实例以接管整个视图,
这是推荐的集成方案,这种方式不需要修改任何现有布局即可实现无侵入式快速集成。
> 示例如下
```kotlin
class MainActivity : AppCompactActivity() {
val binding by lazy { ActivityMainBinding.inflate(layoutInflater) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 在这里注入
PanguTextFactory2.inject(this)
setContentView(binding.root)
}
}
```
::: tip
由于接管了 `LayoutInflater.Factory2`,所以包括类似 `ListView``RecyclerView` 的回收式布局也能被正确接管。
注入 `Activity` 中的 `LayoutInflater` 实例后,以下附属于当前 `Context` 的实例都会自动生效。
- `Fragment`
- `Dialog`
- `PopupWindow`
- `Toast` (在高版本系统中仅前台)
基于 `RemoteView` 的布局将无法生效,因为它们是远程对象,不会使用当前 `Context``LayoutInflater` 进行布局装载。
:::
如果你正在使用 `BetterAndroid` 中的 [ui-compoment → AppBindingActivity](https://betterandroid.github.io/BetterAndroid/KDoc/ui-component/ui-component/com.highcapable.betterandroid.ui.component.activity/-app-binding-activity),你需要稍微改动当前代码。
> 示例如下
```kotlin
class MainActivity : AppBindingActivity<ActivityMainBinding>() {
override fun onPrepareContentView(savedInstanceState: Bundle?): LayoutInflater {
val inflater = super.onPrepareContentView(savedInstanceState)
// 在这里注入
PanguTextFactory2.inject(inflater)
return inflater
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Your code here.
}
}
```
如果你的应用程序没有使用 `AppCompatActivity` 也没有使用 `ViewBinding`,没有关系,你依然可以使用最初的方案进行。
> 示例如下
```kotlin
class MainActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 在这里注入
PanguTextFactory2.inject(this)
setContentView(R.layout.activity_main)
}
}
```
::: tip
`PanguTextFactory2` 除了可以配合 `Activity` 使用,它还支持注入到任何现有的 `LayoutInflater` 实例中,但请在 `LayoutInflater` 实例被用于装载布局前进行注入,否则将无法生效。
:::
#### 手动注入或格式化文本
`PanguText` 同样支持手动注入,你可以在需要的 `TextView``EditText` 上手动进行注入。
> 示例如下
```kotlin
// 假设这就是你的 TextView
val textView: TextView
// 假设这就是你的 EditText
val editText: EditText
// 注入到现有文本
textView.injectPanguText()
editText.injectPanguText()
// 可以选择是否同时注入 Hint (默认是)
textView.injectPanguText(injectHint = false)
editText.injectPanguText(injectHint = false)
// 动态注入,重新调用 setText 也会自动生效
textView.injectRealTimePanguText()
// 动态注入主要针对于 EditText 的输入状态
editText.injectRealTimePanguText()
// 同样可以选择是否同时注入 Hint (默认是)
textView.injectRealTimePanguText(injectHint = false)
editText.injectRealTimePanguText(injectHint = false)
```
`PanguText` 还对 `TextView``setText` 方法进行了扩展,你可以使用如下方式直接设置带有 `PanugText` 样式的文本。
> 示例如下
```kotlin
// 假设这就是你的 TextView
val textView: TextView
// 设置带有 PanguText 样式的文本
textView.setTextWithPangu("Xiaoming今年16岁")
// 设置带有 PanguText 样式的 Hint
textView.setHintWithPangu("输入Xiaoming的年龄")
```
你还可以使用 `PanguText.format` 方法直接格式化文本。
> 示例如下
```kotlin
// 假设这就是你的 TextView
val textView: TextView
// 使用 SpannableString 方案格式化文本
// 需要传入当前 TextView 的 Resources 以及字体大小
// 如果传入的文本自身为 Spannable 类型,则不会创建新的 SpannableString而是返回原始对象
val text = PanguText.format(textView.resources, textView.textSize, "Xiaoming今年16岁")
// 设置文本
textView.text = text
// 直接使用空白字符以插入破坏的方式格式化文本
// 这个方案会为文本增加额外的空白字符 "" (HSP)
// 下方的结果会输出字符串 "Xiaoming今年16岁"
// 你也可以在方法末位自定义要使用的空白字符
val text = PanguText.format("Xiaoming今年16岁")
// 设置文本
textView.text = text
```
::: tip
`injectPanguText``injectRealTimePanguText``setTextWithPangu``setHintWithPangu``PanguText.format` 方法支持 `config` 参数,你可以参考下方的 [个性化配置](#个性化配置)。
:::
#### 自定义 View
`PanguText` 还可以配合自定义 `View` 进行使用,你可以将你的 `View` 继承到 `AppCompatTextView` 并重写 `setText` 方法。
> 示例如下
```kotlin
class MyTextView(context: Context, attrs: AttributeSet? = null) : AppCompatTextView(context, attrs) {
override fun setText(text: CharSequence?, type: BufferType?) {
// 在这里手动进行注入
val panguText = text?.let { PanguText.format(resources, textSize, it) }
super.setText(panguText, type)
}
}
```
::: warning
`TextView` 在注入 `PanguText` 后,如果你在 XML 布局中使用了 `android:singleLine="true"` 或在代码中使用了 `TextView.setSingleLine(true)` 并且配合 `android:elipsize="..."`
那么这种方式设置单行文本可能会造成文本超出屏幕后其中会中显示出无法解析的 `OBJ` 字符 (被省略号截断),因为 `TextView` 不会在测量时计算文本中的 `Span`,这会导致文本宽度计算错误。
解决方案为在 XML 布局中使用 `android:maxLines="1"` 或在代码中使用 `TextView.setMaxLines(1)` 来代替。
> 示例如下
```xml
<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="这是一段很长很长长长长长长长长长长长长还有English混入的的文本"
android:maxLines="1"
android:ellipsize="end" />
```
:::
### 个性化配置
`PanguText` 支持个性化配置,你可以使用全局静态实例 `PanguText.globalConfig` 获取全局配置,或单独进行配置。
> 示例如下
```kotlin
// 获取全局配置
val config = PanguText.globalConfig
// 开关,禁用将使所有功能失效
config.isEnabled = true
// 处理 Spanned 文本
// Spanned 文本处理默认启用,但此功能尚处于实验性阶段,
// 如果发生问题你可以选择禁用,禁用后遇到 Spanned 文本将返回原始文本
config.isProcessedSpanned = true
// 设置在格式化过程中以正则形式定义需要排除的内容
// 例如排除全部 URL
config.excludePatterns.add("https?://\\S+".toRegex())
// 例如排除类似 "[doge]" 的 emoji 占位符,
// 如果你需要使用 [ImageSpan] 显示 emoji 图片,你可以选择排除这些占位符
config.excludePatterns.add("\\[.*?]".toRegex())
// 设置 CJK 空白占位间距比例
// 这会决定最终的排版效果,建议保持默认比例,然后再以此跟随个人喜好进行调整
config.cjkSpacingRatio = 7f
```
::: warning
如果你使用了 [注入布局装载器](#注入布局装载器-layoutinflater) 的方案进行集成,请在 `PanguTextFactory2.inject(...)` 执行前配置 `PanguText.globalConfig`,否则配置将无法生效。
:::
你还可以在手动注入或格式化文本时传入 `config` 参数以进行个性化配置。
> 示例如下
```kotlin
// 假设这就是你的 TextView
val textView: TextView
// 创建一个新配置
// 你可以设置 [copyFromGlobal] 为 false 来不从全局配置中复制配置
val config = PanguTextConfig(copyFromGlobal = false) {
excludePatterns.add("https?://\\S+".toRegex())
excludePatterns.add("\\[.*?]".toRegex())
cjkSpacingRatio = 7f
}
// 你还可以从任意一个配置中复制并创建新配置
val config2 = config.copy {
excludePatterns.clear()
excludePatterns.add("https?://\\S+".toRegex())
excludePatterns.add("\\[.*?]".toRegex())
cjkSpacingRatio = 7f
}
// 手动注入并配置
textView.injectPanguText(config = config2)
```
如果你使用了 [注入布局装载器](#注入布局装载器-layoutinflater) 的方案进行集成,你可以在 `TextView``EditText` 或继承于它们的 XML 布局声明中使用以下属性来进行个性化配置。
- `panguText_enabled` 对应 `PanguTextConfig.isEnabled`
- `panguText_processedSpanned` 对应 `PanguTextConfig.isProcessedSpanned`
- `panguText_excludePatterns` 对应 `PanguTextConfig.excludePatterns`,字符串数组,多个使用 `|@|` 分隔
- `panguText_cjkSpacingRatio` 对应 `PanguTextConfig.cjkSpacingRatio`
> 示例如下
```xml
<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Xiaoming今年16岁"
app:panguText_enabled="true"
app:panguText_processedSpanned="true"
app:panguText_excludePatterns="https?://\\S+;\\[.*?]|@|\\[.*?]"
app:panguText_cjkSpacingRatio="7.0" />
```
::: warning
由于 Android Studio 的问题,上述属性可能不会有补全提示,请自行补全。
不要忘记加入声明 `xmlns:app="http://schemas.android.com/apk/res-auto"`
:::
在自定义 `View` 中,你可以将你的 `View` 继承于 `PanguTextView` 接口以同样实现上述功能。
> 示例如下
```kotlin
class MyTextView(context: Context, attrs: AttributeSet? = null) : AppCompatTextView(context, attrs),
PanguTextView {
override fun configurePanguText(config: PanguTextConfig) {
// 配置你的 [PanguTextConfig]
}
}
```
::: warning
`PanguTextView` 接口的优先级将高于直接在 XML 布局中使用的属性,如果你同时使用了这两种方式进行配置,`PanguTextView` 接口的配置将覆盖 XML 布局中的配置。
单独配置将覆盖全局配置,未配置的选项将跟随全局配置。
:::

View File

@@ -0,0 +1,9 @@
# Jetpack Compose
![Maven Central](https://img.shields.io/maven-central/v/com.highcapable.pangutext/pangutext-compose?logo=apachemaven&logoColor=orange)
<span style="margin-left: 5px"/>
![Maven metadata URL](https://img.shields.io/maven-metadata/v?metadataUrl=https%3A%2F%2Fraw.githubusercontent.com%2FHighCapable%2Fmaven-repository%2Frefs%2Fheads%2Fmain%2Frepository%2Freleases%2Fcom%2Fhighcapable%2Fpangutext%2Fpangutext-compose%2Fmaven-metadata.xml&logo=apachemaven&logoColor=orange&label=highcapable-maven-releases)
这是 Jetpack Compose (多平台) 的核心依赖,在 Jetpack Compose 上使用 `PanguText` 时,你需要引入此模块。
此模块尚在开发阶段,将在后期逐渐进行完善。

2004
docs-source/yarn.lock Normal file

File diff suppressed because it is too large Load Diff