Full Code of draco1023/poi-tl-ext for AI

main 9bb03841fcd3 cached
114 files
616.6 KB
157.4k tokens
681 symbols
1 requests
Download .txt
Showing preview only (670K chars total). Download the full file or copy to clipboard to get everything.
Repository: draco1023/poi-tl-ext
Branch: main
Commit: 9bb03841fcd3
Files: 114
Total size: 616.6 KB

Directory structure:
gitextract_tlgs8gmd/

├── .gitattributes
├── .github/
│   └── ISSUE_TEMPLATE/
│       └── bug_report.md
├── .gitignore
├── LICENSE
├── README.md
├── build.gradle
├── gradle/
│   └── wrapper/
│       └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── settings.gradle
└── src/
    ├── main/
    │   ├── java/
    │   │   └── org/
    │   │       ├── apache/
    │   │       │   └── poi/
    │   │       │       └── xwpf/
    │   │       │           └── usermodel/
    │   │       │               ├── SVGPictureData.java
    │   │       │               └── SVGRelation.java
    │   │       ├── ddr/
    │   │       │   ├── image/
    │   │       │   │   ├── ImageInfo.java
    │   │       │   │   ├── ImageInputStreamWrapper.java
    │   │       │   │   ├── ImageType.java
    │   │       │   │   ├── MetadataReader.java
    │   │       │   │   ├── MetadataReaders.java
    │   │       │   │   ├── avif/
    │   │       │   │   │   ├── AvifImageReader.java
    │   │       │   │   │   ├── AvifImageReaderSpi.java
    │   │       │   │   │   ├── AvifMetadataReader.java
    │   │       │   │   │   └── AvifProviderInfo.java
    │   │       │   │   ├── bmp/
    │   │       │   │   │   └── BmpMetadataReader.java
    │   │       │   │   ├── eps/
    │   │       │   │   │   └── EpsMetadataReader.java
    │   │       │   │   ├── gif/
    │   │       │   │   │   └── GifMetadataReader.java
    │   │       │   │   ├── heif/
    │   │       │   │   │   ├── HeifImageReader.java
    │   │       │   │   │   ├── HeifImageReaderSpi.java
    │   │       │   │   │   ├── HeifMetadataReader.java
    │   │       │   │   │   └── HeifProviderInfo.java
    │   │       │   │   ├── jpeg/
    │   │       │   │   │   └── JpegMetadataReader.java
    │   │       │   │   ├── png/
    │   │       │   │   │   └── PngMetadataReader.java
    │   │       │   │   ├── tiff/
    │   │       │   │   │   └── TiffMetadataReader.java
    │   │       │   │   └── webp/
    │   │       │   │       └── WebpMetadataReader.java
    │   │       │   └── poi/
    │   │       │       ├── html/
    │   │       │       │   ├── ElementRenderer.java
    │   │       │       │   ├── ElementRendererProvider.java
    │   │       │       │   ├── HtmlConstants.java
    │   │       │       │   ├── HtmlRenderConfig.java
    │   │       │       │   ├── HtmlRenderContext.java
    │   │       │       │   ├── HtmlRenderPolicy.java
    │   │       │       │   ├── tag/
    │   │       │       │   │   ├── ARenderer.java
    │   │       │       │   │   ├── BigRenderer.java
    │   │       │       │   │   ├── BoldRenderer.java
    │   │       │       │   │   ├── BreakRenderer.java
    │   │       │       │   │   ├── DeleteRenderer.java
    │   │       │       │   │   ├── FigureCaptionRenderer.java
    │   │       │       │   │   ├── FigureRenderer.java
    │   │       │       │   │   ├── HeaderBreakRenderer.java
    │   │       │       │   │   ├── HeaderRenderer.java
    │   │       │       │   │   ├── ImageRenderer.java
    │   │       │       │   │   ├── ItalicRenderer.java
    │   │       │       │   │   ├── LaTeXRenderer.java
    │   │       │       │   │   ├── ListItemRenderer.java
    │   │       │       │   │   ├── ListRenderer.java
    │   │       │       │   │   ├── MarkRenderer.java
    │   │       │       │   │   ├── MathRenderer.java
    │   │       │       │   │   ├── OmittedRenderer.java
    │   │       │       │   │   ├── PreRenderer.java
    │   │       │       │   │   ├── RubyRenderer.java
    │   │       │       │   │   ├── SmallRenderer.java
    │   │       │       │   │   ├── SubscriptRenderer.java
    │   │       │       │   │   ├── SuperscriptRenderer.java
    │   │       │       │   │   ├── SvgRenderer.java
    │   │       │       │   │   ├── TableCellRenderer.java
    │   │       │       │   │   ├── TableRenderer.java
    │   │       │       │   │   ├── UnderlineRenderer.java
    │   │       │       │   │   └── WalkThroughRenderer.java
    │   │       │       │   └── util/
    │   │       │       │       ├── BoxProperty.java
    │   │       │       │       ├── CSSLength.java
    │   │       │       │       ├── CSSLengthUnit.java
    │   │       │       │       ├── CSSStyleUtils.java
    │   │       │       │       ├── Colors.java
    │   │       │       │       ├── ColumnStyle.java
    │   │       │       │       ├── EmptyCSSStyle.java
    │   │       │       │       ├── InlineStyle.java
    │   │       │       │       ├── JsoupUtils.java
    │   │       │       │       ├── ListStyle.java
    │   │       │       │       ├── ListStyleType.java
    │   │       │       │       ├── NamedBorderWidth.java
    │   │       │       │       ├── NamedFontSize.java
    │   │       │       │       ├── NumberingContext.java
    │   │       │       │       ├── RenderUtils.java
    │   │       │       │       ├── Span.java
    │   │       │       │       ├── SpanWidth.java
    │   │       │       │       ├── WhiteSpaceRule.java
    │   │       │       │       └── XWPFParagraphRuns.java
    │   │       │       ├── latex/
    │   │       │       │   ├── LaTeXRenderPolicy.java
    │   │       │       │   ├── LaTeXUtils.java
    │   │       │       │   ├── TagHandler.java
    │   │       │       │   └── TextCircledHandler.java
    │   │       │       ├── math/
    │   │       │       │   ├── EmptyEOfNaryDisplayMode.java
    │   │       │       │   ├── MathMLRenderPolicy.java
    │   │       │       │   ├── MathMLUtils.java
    │   │       │       │   └── MathRenderConfig.java
    │   │       │       └── util/
    │   │       │           ├── ByteArrayCopyStream.java
    │   │       │           ├── HttpURLConnectionUtils.java
    │   │       │           └── XmlUtils.java
    │   │       └── jsoup/
    │   │           └── parser/
    │   │               └── CustomHtmlTreeBuilder.java
    │   └── resources/
    │       ├── META-INF/
    │       │   └── services/
    │       │       └── javax.imageio.spi.ImageReaderSpi
    │       ├── MML2OMML.XSL
    │       ├── math-character-aliases.txt
    │       └── math-character-circled.txt
    └── test/
        ├── java/
        │   └── org/
        │       └── ddr/
        │           └── poi/
        │               ├── FileReader.java
        │               ├── html/
        │               │   └── HtmlRenderPolicyTest.java
        │               ├── latex/
        │               │   └── LaTeXRenderPolicyTest.java
        │               └── math/
        │                   └── MathMLRenderPolicyTest.java
        └── resources/
            ├── 0.xml
            ├── 1.html
            ├── 1.xml
            ├── 2.html
            ├── 2.xml
            ├── 3.xml
            ├── math.docx
            ├── notes.docx
            ├── poi.docx
            └── simplelogger.properties

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitattributes
================================================
#
# https://help.github.com/articles/dealing-with-line-endings/
#
# These are explicitly windows files and should use crlf
*.bat           text eol=crlf



================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''

---

**Describe the bug**
A clear and concise description of what the bug is.

**HTML content:**
<p>Some html content to reproduce the issue</p>

**Expected behavior**
A clear and concise description of what you expected to happen.

**Screenshots**
If applicable, add screenshots to help explain your problem.

**poi-tl-ext version:**
[e.g. 0.3.12]

**poi-tl version:**
[e.g. 1.9.1]

**Additional context**
Add any other context about the problem here.


================================================
FILE: .gitignore
================================================
# Compiled class file
*.class

# Log file
*.log

# BlueJ files
*.ctxt

# Mobile Tools for Java (J2ME)
.mtj.tmp/

# Package Files #
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
/*.docx

### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans

### IntelliJ IDEA ###
.idea/
*.iws
*.iml
*.ipr

### NetBeans ###
nbproject/private/
!dev/build/*
build/*
nbbuild/
dist/
nbdist/
.nb-gradle/

# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*

# Ignore Gradle project-specific cache directory
.gradle/
gradle.properties

# Ignore Gradle build output directory
build/
out/
.gradletasknamecache
secring.gpg


================================================
FILE: LICENSE
================================================
                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "[]"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright [yyyy] [name of copyright owner]

   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

       http://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.


================================================
FILE: README.md
================================================
# poi-tl-ext
![GitHub](https://img.shields.io/github/license/draco1023/poi-tl-ext) ![JDK](https://img.shields.io/badge/jdk-1.8-blue)

# Maven

poi 4.x poi-tl 1.11 以前的版本

```xml
<dependency>
    <groupId>io.github.draco1023</groupId>
    <artifactId>poi-tl-ext</artifactId>
    <version>0.4.26</version>
</dependency>
```

poi 5.x poi-tl 1.11.0+

```xml
<dependency>
    <groupId>io.github.draco1023</groupId>
    <artifactId>poi-tl-ext</artifactId>
    <version>0.4.26-poi5</version>
</dependency>
```

# 扩展功能

在 [poi-tl](https://github.com/Sayi/poi-tl) 的基础上扩展了如下功能:

- 支持渲染`HTML`字符串,插件`HtmlRenderPolicy`的使用方法如下(也可参考[文档](http://deepoove.com/poi-tl/#_%E4%BD%BF%E7%94%A8%E6%8F%92%E4%BB%B6))

  ```java
  HtmlRenderPolicy htmlRenderPolicy = new HtmlRenderPolicy();
  Configure configure = Configure.builder()
          .bind("key", htmlRenderPolicy)
          .build();
  Map<String, Object> data = new HashMap<>();
  data.put("key", "<p>Hello <b>world</b>!</p>");
  XWPFTemplate.compile("input.docx", configure).render(data).writeToFile("output.docx");
  ```
  
  `HtmlRenderPolicy`可以通过`HtmlRenderConfig`进行如下设置:
  - `globalFont` 全局默认字体(用于归一化处理,而不是用于样式兜底)
  - `globalFontSize` 全局默认字号(用于归一化处理,而不是用于样式兜底)
  - `showDefaultTableBorderInTableCell` 是否显示嵌套表格的边框(`poi`生成嵌套表格时默认不显示边框,见[#12](https://github.com/draco1023/poi-tl-ext/issues/12))
  - `numberingIndent` 多级列表项缩进长度,默认值360
  - `numberingHanging` 列表项悬挂长度,默认值360,CSS样式`list-style-position`为`inside`时该参数无效
  - `numberingSpacing` 列表编号与内容之间的间隔类型,`STLevelSuffix.NOTHING`/`STLevelSuffix.SPACE`/`STLevelSuffix.TAB`
  
  自定义`<latex>`标签,允许渲染嵌入在`HTML`中的`LaTeX`,字符串格式可参考[文档](https://www2.ph.ed.ac.uk/snuggletex/documentation/supported-latex.html)。
  
  _目前实现了富文本编辑器可实现的大部分效果,后续继续改进..._

- 支持渲染`MathML`字符串,插件类为`MathMLRenderPolicy`
- 支持渲染`LaTeX`字符串,插件类为`LaTeXRenderPolicy`

## 支持我
如果您觉得这个插件节省了您的时间和精力,或者解决了您的难题,可以考虑支持一下我的工作,感谢! ⚡⚡⚡

![wechat_sp](https://pub-1085551b511e4719a277177fe3c8f95b.r2.dev/wechat_sp.jpg)

================================================
FILE: build.gradle
================================================
/*
 * Copyright 2016 - 2021 Draco, https://github.com/draco1023
 *
 * 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
 *
 *     http://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.
 */

plugins {
    id 'java-library'
    id 'io.github.sgtsilvio.gradle.maven-central-publishing' version '0.4.0'
}

def getBranchName() {
    providers.exec {
        commandLine("git", "branch", "--show-current")
    }.standardOutput.asText.get().trim()
}

ext {
    VERSION_NAME = 'poi-5' == getBranchName()? "${project.V}-poi5" : project.V
}

group = GROUP
version = VERSION_NAME

repositories {
    mavenLocal()
    jcenter()
    mavenCentral()
}

dependencies {
    api 'com.deepoove:poi-tl:1.9.1'
    api 'org.apache.poi:ooxml-schemas:1.4'
    api 'org.apache.commons:commons-lang3:3.10'
    api 'commons-io:commons-io:2.11.0'
    implementation 'net.sourceforge.cssparser:cssparser:0.9.29'
    implementation 'org.jsoup:jsoup:1.15.3'
    implementation 'net.sf.saxon:Saxon-HE:11.4'
    implementation 'de.rototor.snuggletex:snuggletex-core:1.3.0'
    implementation 'com.drewnoakes:metadata-extractor:2.19.0'
    implementation 'com.twelvemonkeys.imageio:imageio-batik:3.10.1'
    implementation 'org.apache.xmlgraphics:batik-rasterizer-ext:1.17'
    implementation 'com.twelvemonkeys.imageio:imageio-webp:3.10.1'
    implementation 'com.twelvemonkeys.imageio:imageio-pict:3.10.1'
    implementation 'com.twelvemonkeys.imageio:imageio-tiff:3.10.1'
    implementation 'com.twelvemonkeys.imageio:imageio-jpeg:3.10.1'
    implementation 'com.twelvemonkeys.imageio:imageio-bmp:3.10.1'

    compileOnly 'com.google.code.findbugs:jsr305:3.0.2'

    testImplementation 'org.junit.jupiter:junit-jupiter-api:5.6.2'
    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.6.2'
    testRuntimeOnly 'org.slf4j:slf4j-simple:1.7.7'
}

sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8

test {
    useJUnitPlatform()
}

tasks.withType(JavaCompile) {
    options.encoding = 'UTF-8'
    options.fork = true
}

javadoc {
    options {
        encoding 'UTF-8'
        charSet 'UTF-8'
    }
}
javadoc.options.addBooleanOption('Xdoclint:none', true)

java {
    withSourcesJar()
    withJavadocJar()
}

publishing {
    publications {
        maven(MavenPublication) {
            from components.java
            groupId = GROUP
            artifactId = POM_ARTIFACT_ID
            version = VERSION_NAME
            pom {
                name = POM_NAME
                description = POM_DESCRIPTION
                url = POM_URL
                licenses {
                    license {
                        name = POM_LICENCE_NAME
                        url = POM_LICENCE_URL
                    }
                }
                developers {
                    developer {
                        id = POM_DEVELOPER_ID
                        name = POM_DEVELOPER_NAME
                        url = POM_DEVELOPER_URL
                    }
                }
                scm {
                    connection = POM_SCM_CONNECTION
                    developerConnection = POM_SCM_DEV_CONNECTION
                    url = POM_SCM_URL
                }
            }
        }
    }
}

signing {
    sign publishing.publications.maven
}

================================================
FILE: gradle/wrapper/gradle-wrapper.properties
================================================
#
# Copyright 2016 - 2021 Draco, https://github.com/draco1023
#
# 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
#
#     http://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.
#
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists


================================================
FILE: gradlew
================================================
#!/usr/bin/env sh

#
# Copyright 2015 the original author or authors.
#
# 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.
#

##############################################################################
##
##  Gradle start up script for UN*X
##
##############################################################################

# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
    ls=`ls -ld "$PRG"`
    link=`expr "$ls" : '.*-> \(.*\)$'`
    if expr "$link" : '/.*' > /dev/null; then
        PRG="$link"
    else
        PRG=`dirname "$PRG"`"/$link"
    fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null

APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`

# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Dfile.encoding=UTF-8" "-Xmx64m" "-Xms64m"'

# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"

warn () {
    echo "$*"
}

die () {
    echo
    echo "$*"
    echo
    exit 1
}

# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
  CYGWIN* )
    cygwin=true
    ;;
  Darwin* )
    darwin=true
    ;;
  MINGW* )
    msys=true
    ;;
  NONSTOP* )
    nonstop=true
    ;;
esac

CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar


# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
        # IBM's JDK on AIX uses strange locations for the executables
        JAVACMD="$JAVA_HOME/jre/sh/java"
    else
        JAVACMD="$JAVA_HOME/bin/java"
    fi
    if [ ! -x "$JAVACMD" ] ; then
        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME

Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
    fi
else
    JAVACMD="java"
    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.

Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi

# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
    MAX_FD_LIMIT=`ulimit -H -n`
    if [ $? -eq 0 ] ; then
        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
            MAX_FD="$MAX_FD_LIMIT"
        fi
        ulimit -n $MAX_FD
        if [ $? -ne 0 ] ; then
            warn "Could not set maximum file descriptor limit: $MAX_FD"
        fi
    else
        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
    fi
fi

# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi

# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
    
    JAVACMD=`cygpath --unix "$JAVACMD"`

    # We build the pattern for arguments to be converted via cygpath
    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
    SEP=""
    for dir in $ROOTDIRSRAW ; do
        ROOTDIRS="$ROOTDIRS$SEP$dir"
        SEP="|"
    done
    OURCYGPATTERN="(^($ROOTDIRS))"
    # Add a user-defined pattern to the cygpath arguments
    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
    fi
    # Now convert the arguments - kludge to limit ourselves to /bin/sh
    i=0
    for arg in "$@" ; do
        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option

        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
        else
            eval `echo args$i`="\"$arg\""
        fi
        i=`expr $i + 1`
    done
    case $i in
        0) set -- ;;
        1) set -- "$args0" ;;
        2) set -- "$args0" "$args1" ;;
        3) set -- "$args0" "$args1" "$args2" ;;
        4) set -- "$args0" "$args1" "$args2" "$args3" ;;
        5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
        6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
        7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
        8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
        9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
    esac
fi

# Escape application args
save () {
    for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
    echo " "
}
APP_ARGS=`save "$@"`

# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"

exec "$JAVACMD" "$@"


================================================
FILE: gradlew.bat
================================================
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem      https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem

@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem  Gradle startup script for Windows
@rem
@rem ##########################################################################

@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal

set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%

@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi

@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Dfile.encoding=UTF-8" "-Xmx64m" "-Xms64m"

@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome

set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init

echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.

goto fail

:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe

if exist "%JAVA_EXE%" goto init

echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.

goto fail

:init
@rem Get command-line arguments, handling Windows variants

if not "%OS%" == "Windows_NT" goto win9xME_args

:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2

:win9xME_args_slurp
if "x%~1" == "x" goto execute

set CMD_LINE_ARGS=%*

:execute
@rem Setup the command line

set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar


@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%

:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd

:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1

:mainEnd
if "%OS%"=="Windows_NT" endlocal

:omega


================================================
FILE: settings.gradle
================================================
/*
 * Copyright 2016 - 2021 Draco, https://github.com/draco1023
 *
 * 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
 *
 *     http://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.
 */

rootProject.name = 'poi-tl-ext'


================================================
FILE: src/main/java/org/apache/poi/xwpf/usermodel/SVGPictureData.java
================================================
package org.apache.poi.xwpf.usermodel;

import org.apache.poi.openxml4j.opc.PackagePart;

/**
 * SVGPictureData
 *
 * @author Draco
 * @since 2022-04-12
 */
public class SVGPictureData extends XWPFPictureData {

    public static final int PICTURE_TYPE_SVG = 1;

    public SVGPictureData() {
    }

    public SVGPictureData(PackagePart part) {
        super(part);
    }

    public static void initRelation() {
        RELATIONS[PICTURE_TYPE_SVG] = SVGRelation.INSTANCE;
    }
}


================================================
FILE: src/main/java/org/apache/poi/xwpf/usermodel/SVGRelation.java
================================================
package org.apache.poi.xwpf.usermodel;

import org.apache.poi.ooxml.POIXMLRelation;
import org.apache.poi.openxml4j.opc.PackageRelationshipTypes;
import org.apache.poi.sl.usermodel.PictureData;

import javax.xml.namespace.QName;

/**
 * SVGRelation
 *
 * @author Draco
 * @since 2022-04-12
 */
public class SVGRelation extends POIXMLRelation {
    public static final SVGRelation INSTANCE = new SVGRelation();
    /**
     * @see org.apache.poi.xslf.usermodel.XSLFPictureShape#MS_SVG_NS
     */
    public static final String MS_SVG_NS = "http://schemas.microsoft.com/office/drawing/2016/SVG/main";
    /**
     * @see org.apache.poi.xslf.usermodel.XSLFPictureShape#SVG_URI
     */
    public static final String SVG_URI = "{96DAC541-7B7A-43D3-8B79-37D633B846F1}";
    /**
     * @see org.apache.poi.xslf.usermodel.XSLFPictureShape#EMBED_TAG
     */
    public static final QName EMBED_TAG = new QName(PackageRelationshipTypes.CORE_PROPERTIES_ECMA376_NS,
            "embed", "r");

    public static final String SVG_BLIP = "svgBlip";

    public static final String SVG_PREFIX = "asvg";

    public static final QName SVG_QNAME = new QName(MS_SVG_NS, SVG_BLIP, SVG_PREFIX);

    /**
     * @see XWPFRelation#IMAGE_PNG
     */
    private SVGRelation() {
        super(PictureData.PictureType.SVG.contentType,
                PackageRelationshipTypes.IMAGE_PART,
                "/word/media/image#.svg",
                SVGPictureData::new, SVGPictureData::new, null);
    }
}


================================================
FILE: src/main/java/org/ddr/image/ImageInfo.java
================================================
package org.ddr.image;

import java.awt.*;
import java.io.ByteArrayInputStream;

public class ImageInfo {
    private ByteArrayInputStream stream;
    private ImageType type;
    private Dimension dimension;

    public ImageInfo(ByteArrayInputStream stream) {
        this.stream = stream;
    }

    public ImageInfo(ByteArrayInputStream stream, ImageType type, Dimension dimension) {
        this.stream = stream;
        this.type = type;
        this.dimension = dimension;
    }

    public ByteArrayInputStream getStream() {
        return stream;
    }

    public void setStream(ByteArrayInputStream stream) {
        this.stream = stream;
    }

    public ImageType getType() {
        return type;
    }

    public void setType(ImageType type) {
        this.type = type;
    }

    public Dimension getDimension() {
        return dimension;
    }

    public void setDimension(Dimension dimension) {
        this.dimension = dimension;
    }

    public int getWidth() {
        return dimension == null ? 0 : dimension.width;
    }

    public int getHeight() {
        return dimension == null ? 0 : dimension.height;
    }

    public int getRawType() {
        return type == null ? -1 : type.getType();
    }
}


================================================
FILE: src/main/java/org/ddr/image/ImageInputStreamWrapper.java
================================================
package org.ddr.image;

import javax.imageio.stream.IIOByteBuffer;
import javax.imageio.stream.ImageInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteOrder;

public class ImageInputStreamWrapper extends InputStream implements ImageInputStream {

    private final ImageInputStream input;

    public ImageInputStreamWrapper(ImageInputStream input) {
        this.input = input;
    }

    @Override
    public void setByteOrder(ByteOrder byteOrder) {
        input.setByteOrder(byteOrder);
    }

    @Override
    public ByteOrder getByteOrder() {
        return input.getByteOrder();
    }

    @Override
    public int read() throws IOException {
        return input.read();
    }

    @Override
    public void readBytes(IIOByteBuffer buf, int len) throws IOException {
        input.readBytes(buf, len);
    }

    @Override
    public boolean readBoolean() throws IOException {
        return input.readBoolean();
    }

    @Override
    public byte readByte() throws IOException {
        return input.readByte();
    }

    @Override
    public int readUnsignedByte() throws IOException {
        return input.readUnsignedByte();
    }

    @Override
    public short readShort() throws IOException {
        return input.readShort();
    }

    @Override
    public int readUnsignedShort() throws IOException {
        return input.readUnsignedShort();
    }

    @Override
    public char readChar() throws IOException {
        return input.readChar();
    }

    @Override
    public int readInt() throws IOException {
        return input.readInt();
    }

    @Override
    public long readUnsignedInt() throws IOException {
        return input.readUnsignedInt();
    }

    @Override
    public long readLong() throws IOException {
        return input.readLong();
    }

    @Override
    public float readFloat() throws IOException {
        return input.readFloat();
    }

    @Override
    public double readDouble() throws IOException {
        return input.readDouble();
    }

    @Override
    public String readLine() throws IOException {
        return input.readLine();
    }

    @Override
    public String readUTF() throws IOException {
        return input.readUTF();
    }

    @Override
    public void readFully(byte[] b, int off, int len) throws IOException {
        input.readFully(b, off, len);
    }

    @Override
    public void readFully(byte[] b) throws IOException {
        input.readFully(b);
    }

    @Override
    public void readFully(short[] s, int off, int len) throws IOException {
        input.readFully(s, off, len);
    }

    @Override
    public void readFully(char[] c, int off, int len) throws IOException {
        input.readFully(c, off, len);
    }

    @Override
    public void readFully(int[] i, int off, int len) throws IOException {
        input.readFully(i, off, len);
    }

    @Override
    public void readFully(long[] l, int off, int len) throws IOException {
        input.readFully(l, off, len);
    }

    @Override
    public void readFully(float[] f, int off, int len) throws IOException {
        input.readFully(f, off, len);
    }

    @Override
    public void readFully(double[] d, int off, int len) throws IOException {
        input.readFully(d, off, len);
    }

    @Override
    public long getStreamPosition() throws IOException {
        return input.getStreamPosition();
    }

    @Override
    public int getBitOffset() throws IOException {
        return input.getBitOffset();
    }

    @Override
    public void setBitOffset(int bitOffset) throws IOException {
        input.setBitOffset(bitOffset);
    }

    @Override
    public int readBit() throws IOException {
        return input.readBit();
    }

    @Override
    public long readBits(int numBits) throws IOException {
        return input.readBits(numBits);
    }

    @Override
    public long length() throws IOException {
        return input.length();
    }

    @Override
    public int skipBytes(int n) throws IOException {
        return input.skipBytes(n);
    }

    @Override
    public long skipBytes(long n) throws IOException {
        return input.skipBytes(n);
    }

    @Override
    public void seek(long pos) throws IOException {
        input.seek(pos);
    }

    @Override
    public void mark() {
        input.mark();
    }

    @Override
    public void flushBefore(long pos) throws IOException {
        input.flushBefore(pos);
    }

    @Override
    public void flush() throws IOException {
        input.flush();
    }

    @Override
    public long getFlushedPosition() {
        return input.getFlushedPosition();
    }

    @Override
    public boolean isCached() {
        return input.isCached();
    }

    @Override
    public boolean isCachedMemory() {
        return input.isCachedMemory();
    }

    @Override
    public boolean isCachedFile() {
        return input.isCachedFile();
    }
}


================================================
FILE: src/main/java/org/ddr/image/ImageType.java
================================================
package org.ddr.image;

import org.apache.poi.xwpf.usermodel.Document;

public enum ImageType {
    EMF(Document.PICTURE_TYPE_EMF),
    WMF(Document.PICTURE_TYPE_WMF),
    PICT(Document.PICTURE_TYPE_PICT),
    JPEG(Document.PICTURE_TYPE_JPEG),
    JPG(Document.PICTURE_TYPE_JPEG),
    PNG(Document.PICTURE_TYPE_PNG),
    DIB(Document.PICTURE_TYPE_DIB),
    GIF(Document.PICTURE_TYPE_GIF),
    TIF(Document.PICTURE_TYPE_TIFF),
    TIFF(Document.PICTURE_TYPE_TIFF),
    EPS(Document.PICTURE_TYPE_EPS),
    BMP(Document.PICTURE_TYPE_BMP),
    WPG(Document.PICTURE_TYPE_WPG);

    private final int type;

    ImageType(int type) {
        this.type = type;
    }

    public String getExtension() {
        return name().toLowerCase();
    }

    public int getType() {
        return type;
    }
}


================================================
FILE: src/main/java/org/ddr/image/MetadataReader.java
================================================
package org.ddr.image;

import com.drew.imaging.FileType;
import com.drew.metadata.Metadata;

import java.awt.*;

public interface MetadataReader {
    boolean canRead(FileType type);

    ImageType getType(Metadata metadata);

    Dimension getDimension(Metadata metadata);
}


================================================
FILE: src/main/java/org/ddr/image/MetadataReaders.java
================================================
package org.ddr.image;

import org.ddr.image.avif.AvifMetadataReader;
import org.ddr.image.bmp.BmpMetadataReader;
import org.ddr.image.eps.EpsMetadataReader;
import org.ddr.image.gif.GifMetadataReader;
import org.ddr.image.heif.HeifMetadataReader;
import org.ddr.image.jpeg.JpegMetadataReader;
import org.ddr.image.png.PngMetadataReader;
import org.ddr.image.tiff.TiffMetadataReader;
import org.ddr.image.webp.WebpMetadataReader;

public class MetadataReaders {
    public static final MetadataReader[] INSTANCES = {
            new JpegMetadataReader(),
            new PngMetadataReader(),
            new GifMetadataReader(),
            new WebpMetadataReader(),
            new AvifMetadataReader(),
            new HeifMetadataReader(),
            new BmpMetadataReader(),
            new TiffMetadataReader(),
            new EpsMetadataReader()
    };
}


================================================
FILE: src/main/java/org/ddr/image/avif/AvifImageReader.java
================================================
package org.ddr.image.avif;

import org.ddr.image.heif.HeifImageReader;

import javax.imageio.spi.ImageReaderSpi;

public class AvifImageReader extends HeifImageReader {

    public AvifImageReader(ImageReaderSpi provider) {
        super(provider, new AvifMetadataReader(), "avif");
    }
}


================================================
FILE: src/main/java/org/ddr/image/avif/AvifImageReaderSpi.java
================================================
package org.ddr.image.avif;

import com.twelvemonkeys.imageio.spi.ImageReaderSpiBase;

import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream;
import java.io.IOException;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;


public final class AvifImageReaderSpi extends ImageReaderSpiBase {
    private static final Set<String> TYPES;

    static {
        TYPES = new HashSet<>(4);
//        TYPES.add("mif1");
//        TYPES.add("msf1");
        TYPES.add("miaf");
        TYPES.add("avif");
        TYPES.add("avis");
        TYPES.add("avio");
    }
    public AvifImageReaderSpi() {
        super(new AvifProviderInfo());
    }

    @Override
    public boolean canDecodeInput(Object source) throws IOException {
        return source instanceof ImageInputStream && canDecode((ImageInputStream) source);
    }

    private boolean canDecode(ImageInputStream input) throws IOException {
        try {
            input.mark();
            for (int i = 0; i < 4; i++) {
                input.read();
            }
            if (input.read() == 'f' && input.read() == 't' && input.read() == 'y' && input.read() == 'p') {
                byte[] bytes = new byte[4];
                int length = input.read(bytes);
                if (length == 4) {
                    String s = new String(bytes);
                    return TYPES.contains(s);
                }
            }
        } catch (Exception ignored) {
        } finally {
            input.reset();
        }
        return false;
    }

    @Override
    public ImageReader createReaderInstance(Object extension) throws IOException {
        return new AvifImageReader(this);
    }

    @Override
    public String getDescription(Locale locale) {
        return "AV1 Image File (AVIF) format image reader";
    }
}


================================================
FILE: src/main/java/org/ddr/image/avif/AvifMetadataReader.java
================================================
package org.ddr.image.avif;

import com.drew.imaging.FileType;
import org.ddr.image.heif.HeifMetadataReader;

public class AvifMetadataReader extends HeifMetadataReader {
    @Override
    public boolean canRead(FileType type) {
        // FIXME metadata-extractor 一直未发版支持 AVIF 格式,会被归为 QuickTime 格式
        return type == FileType.QuickTime || type == FileType.Heif;
    }

}


================================================
FILE: src/main/java/org/ddr/image/avif/AvifProviderInfo.java
================================================
package org.ddr.image.avif;

import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;

final class AvifProviderInfo extends ReaderWriterProviderInfo {
    AvifProviderInfo() {
        super(
                AvifProviderInfo.class,
                new String[]{"avif", "AVIF"}, // Names
                new String[]{"avif", "avifs"}, // Suffixes
                new String[]{"image/avif", "image/avifs"}, // Mime-types
                "org.ddr.image.avif.AvifImageReader", // Reader class name
                new String[]{"org.ddr.image.avif.AvifImageReaderSpi"},
                null,
                null,
                false, null, null, null, null,
                true, null, null, null, null
        );
    }
}


================================================
FILE: src/main/java/org/ddr/image/bmp/BmpMetadataReader.java
================================================
package org.ddr.image.bmp;

import com.drew.imaging.FileType;
import com.drew.metadata.Directory;
import com.drew.metadata.Metadata;
import com.drew.metadata.bmp.BmpHeaderDirectory;
import org.ddr.image.ImageType;
import org.ddr.image.MetadataReader;

import java.awt.*;

public class BmpMetadataReader implements MetadataReader {
    @Override
    public boolean canRead(FileType type) {
        return type == FileType.Bmp;
    }

    @Override
    public ImageType getType(Metadata metadata) {
        return ImageType.BMP;
    }

    @Override
    public Dimension getDimension(Metadata metadata) {
        for (Directory directory : metadata.getDirectories()) {
            if (directory instanceof BmpHeaderDirectory) {
                Integer width = directory.getInteger(BmpHeaderDirectory.TAG_IMAGE_WIDTH);
                Integer height = directory.getInteger(BmpHeaderDirectory.TAG_IMAGE_HEIGHT);
                if (width != null && height != null) {
                    return new Dimension(width, height);
                }
            }
        }
        return null;
    }
}


================================================
FILE: src/main/java/org/ddr/image/eps/EpsMetadataReader.java
================================================
package org.ddr.image.eps;

import com.drew.imaging.FileType;
import com.drew.metadata.Directory;
import com.drew.metadata.Metadata;
import com.drew.metadata.eps.EpsDirectory;
import org.ddr.image.ImageType;
import org.ddr.image.MetadataReader;

import java.awt.*;

public class EpsMetadataReader implements MetadataReader{
    @Override
    public boolean canRead(FileType type) {
        return type == FileType.Eps;
    }

    @Override
    public ImageType getType(Metadata metadata) {
        return ImageType.EPS;
    }

    @Override
    public Dimension getDimension(Metadata metadata) {
        for (Directory directory : metadata.getDirectories()) {
            if (directory instanceof EpsDirectory) {
                Integer width = directory.getInteger(EpsDirectory.TAG_IMAGE_WIDTH);
                Integer height = directory.getInteger(EpsDirectory.TAG_IMAGE_HEIGHT);
                if (width != null && height != null) {
                    return new Dimension(width, height);
                }
            }
        }
        return null;
    }
}


================================================
FILE: src/main/java/org/ddr/image/gif/GifMetadataReader.java
================================================
package org.ddr.image.gif;

import com.drew.imaging.FileType;
import com.drew.metadata.Directory;
import com.drew.metadata.Metadata;
import com.drew.metadata.gif.GifHeaderDirectory;
import org.ddr.image.ImageType;
import org.ddr.image.MetadataReader;

import java.awt.*;

public class GifMetadataReader implements MetadataReader{
    @Override
    public boolean canRead(FileType type) {
        return type == FileType.Gif;
    }

    @Override
    public ImageType getType(Metadata metadata) {
        return ImageType.GIF;
    }

    @Override
    public Dimension getDimension(Metadata metadata) {
        for (Directory directory : metadata.getDirectories()) {
            if (directory instanceof GifHeaderDirectory) {
                Integer width = directory.getInteger(GifHeaderDirectory.TAG_IMAGE_WIDTH);
                Integer height = directory.getInteger(GifHeaderDirectory.TAG_IMAGE_HEIGHT);
                if (width != null && height != null) {
                    return new Dimension(width, height);
                }
            }
        }
        return null;
    }
}


================================================
FILE: src/main/java/org/ddr/image/heif/HeifImageReader.java
================================================
package org.ddr.image.heif;

import com.drew.imaging.FileType;
import com.drew.imaging.ImageMetadataReader;
import com.drew.imaging.ImageProcessingException;
import com.drew.metadata.Metadata;
import com.twelvemonkeys.imageio.ImageReaderBase;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.ddr.image.ImageInputStreamWrapper;
import org.ddr.image.MetadataReader;
import org.ddr.poi.util.HttpURLConnectionUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Element;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.imageio.ImageIO;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.spi.ImageReaderSpi;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;

public class HeifImageReader extends ImageReaderBase {
    private static final Logger log = LoggerFactory.getLogger(HeifImageReader.class);
    protected final MetadataReader metadataReader;

    protected ImageInputStreamWrapper wrapper;
    protected Metadata metadata;
    protected Dimension dimension;
    protected String format;

    public HeifImageReader(ImageReaderSpi provider) {
        this(provider, new HeifMetadataReader(), "heic");
    }

    protected HeifImageReader(ImageReaderSpi provider, MetadataReader metadataReader, String format) {
        super(provider);
        this.metadataReader = metadataReader;
        this.format = format;
    }

    @Override
    protected void resetMembers() {
        metadata = null;
        dimension = null;
    }

    @Override
    public void setInput(Object input, boolean seekForwardOnly, boolean ignoreMetadata) {
        super.setInput(input, seekForwardOnly, ignoreMetadata);

        if (imageInput != null) {
            wrapper = new ImageInputStreamWrapper(imageInput);
            try {
                metadata = ImageMetadataReader.readMetadata(wrapper, 0, FileType.Heif);
                dimension = metadataReader.getDimension(metadata);
            } catch (IOException | ImageProcessingException e) {
                log.warn("Failed to read metadata", e);
            }
        }
    }

    @Override
    public int getWidth(int imageIndex) throws IOException {
        return dimension == null ? 0 : dimension.width;
    }

    @Override
    public int getHeight(int imageIndex) throws IOException {
        return dimension == null ? 0 : dimension.height;
    }

    @Override
    public Iterator<ImageTypeSpecifier> getImageTypes(int imageIndex) throws IOException {
        return null;
    }

    @Override
    public BufferedImage read(int imageIndex, ImageReadParam param) throws IOException {
        return convert(param);
    }

    BufferedImage convert(ImageReadParam param) {
        HttpURLConnection uploadConnection = null;
        HttpURLConnection convertConnection = null;
        HttpURLConnection downloadConnection = null;

        try {
            uploadConnection = HttpURLConnectionUtils.connect("https://ezgif.com/" + format + "-to-jpg");
            uploadConnection.setInstanceFollowRedirects(false);
            HttpURLConnectionUtils.initUserAgent(uploadConnection);
            uploadConnection.setRequestProperty("Referer", "https://ezgif.com/" + format + "-to-jpg");
            String boundary = HttpURLConnectionUtils.initFormData(uploadConnection);

            try (OutputStream outputStream = uploadConnection.getOutputStream()) {
                byte[] boundaryBytes = ("--" + boundary).getBytes();
                wrapper.seek(0);
                HttpURLConnectionUtils.addFormData(outputStream, boundaryBytes, "new-image", "some." + format, wrapper);

                outputStream.write(boundaryBytes);
                outputStream.write("--".getBytes());
                outputStream.write(HttpURLConnectionUtils.newLineBytes);
                outputStream.flush();
            }

            // 获取上传响应
            int uploadResponseCode = uploadConnection.getResponseCode();
            if (uploadResponseCode == HttpURLConnection.HTTP_MOVED_TEMP) {
                String location = uploadConnection.getHeaderField("Location");
                String convertUrl = StringUtils.substringBeforeLast(location, ".");
                String fileId = StringUtils.substringAfterLast(convertUrl, "/");
                convertUrl += "?ajax=true";

                if (log.isDebugEnabled()) {
                    log.debug("{} uploaded: {}", format, fileId);
                }
                convertConnection = HttpURLConnectionUtils.connect(convertUrl);
                HttpURLConnectionUtils.initUserAgent(convertConnection);
                convertConnection.setRequestProperty("Referer", location);
                boundary = HttpURLConnectionUtils.initFormData(convertConnection);
                try (OutputStream convertOutput = convertConnection.getOutputStream()) {
                    byte[] boundaryBytes = ("--" + boundary).getBytes();

                    HttpURLConnectionUtils.addFormData(convertOutput, boundaryBytes, "file", fileId, null);
                    HttpURLConnectionUtils.addFormData(convertOutput, boundaryBytes, "percentage", "90", null);
                    HttpURLConnectionUtils.addFormData(convertOutput, boundaryBytes, "percentager", "90", null);
                    HttpURLConnectionUtils.addFormData(convertOutput, boundaryBytes, "background", "#ffffff", null);
                    HttpURLConnectionUtils.addFormData(convertOutput, boundaryBytes, "backgroundc", "#ffffff", null);
                    HttpURLConnectionUtils.addFormData(convertOutput, boundaryBytes, "ajax", "true", null);

                    convertOutput.write(boundaryBytes);
                    convertOutput.write("--".getBytes());
                    convertOutput.write(HttpURLConnectionUtils.newLineBytes);
                    convertOutput.flush();
                }
                int convertResponseCode = convertConnection.getResponseCode();
                if (convertResponseCode == HttpURLConnection.HTTP_OK) {
                    try (InputStream convertResponse = convertConnection.getInputStream()) {
                        Element body = Jsoup.parse(convertResponse, StandardCharsets.UTF_8.name(), "").body();
                        if (log.isDebugEnabled()) {
                            log.debug("{} converted: {}", format, body.html());
                        }
                        for (Element img : body.select("img")) {
                            String src = img.attr("src");
                            if (StringUtils.contains(src, "ezgif")) {
                                String url = "https:" + src;
                                downloadConnection = HttpURLConnectionUtils.connect(url);
                                HttpURLConnectionUtils.initUserAgent(downloadConnection);
                                try (InputStream downloadResponse = downloadConnection.getInputStream()) {
                                    return ImageIO.read(downloadResponse);
                                }
                            }
                        }
                    }
                } else {
                    log.warn("Failed to convert {} image. Response code: {}", format, convertResponseCode);
                }
            } else {
                log.warn("Failed to upload image. Response code: {}", uploadResponseCode);
            }

        } catch (Exception e) {
            log.warn("Failed to convert {} image", format, e);
            IOUtils.close(uploadConnection);
            IOUtils.close(convertConnection);
            IOUtils.close(downloadConnection);
        }

        return null;
    }
}


================================================
FILE: src/main/java/org/ddr/image/heif/HeifImageReaderSpi.java
================================================
package org.ddr.image.heif;

import com.twelvemonkeys.imageio.spi.ImageReaderSpiBase;

import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream;
import java.io.IOException;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;


public final class HeifImageReaderSpi extends ImageReaderSpiBase {
    private static final Set<String> TYPES;

    static {
        TYPES = new HashSet<>(8);
        TYPES.add("mif1");
        TYPES.add("msf1");
        TYPES.add("heic");
        TYPES.add("heix");
        TYPES.add("hevc");
        TYPES.add("hevx");
    }
    public HeifImageReaderSpi() {
        super(new HeifProviderInfo());
    }

    @Override
    public boolean canDecodeInput(Object source) throws IOException {
        return source instanceof ImageInputStream && canDecode((ImageInputStream) source);
    }

    private boolean canDecode(ImageInputStream input) throws IOException {
        // https://docs.oracle.com/javase/7/docs/technotes/guides/imageio/spec/extending.fm3.html
        // https://github.com/strukturag/libheif/blob/e64bb552f5d48fee5daf69c8c2fd59ec3eee0818/libheif/heif.cc#L102
        // https://devstreaming-cdn.apple.com/videos/wwdc/2017/513fzgbviu23l/513/513_high_efficiency_image_file_format.pdf?dl=1
        try {
            input.mark();
            for (int i = 0; i < 4; i++) {
                input.read();
            }
            if (input.read() == 'f' && input.read() == 't' && input.read() == 'y' && input.read() == 'p') {
                byte[] bytes = new byte[4];
                int length = input.read(bytes);
                if (length == 4) {
                    String s = new String(bytes);
                    return TYPES.contains(s);
                }
            }
        } catch (Exception ignored) {
        } finally {
            input.reset();
        }
        return false;
    }

    @Override
    public ImageReader createReaderInstance(Object extension) throws IOException {
        return new HeifImageReader(this);
    }

    @Override
    public String getDescription(Locale locale) {
        return "High Efficiency Image File (HEIF) format image reader";
    }
}


================================================
FILE: src/main/java/org/ddr/image/heif/HeifMetadataReader.java
================================================
package org.ddr.image.heif;

import com.drew.imaging.FileType;
import com.drew.metadata.Directory;
import com.drew.metadata.Metadata;
import com.drew.metadata.exif.ExifDescriptorBase;
import com.drew.metadata.exif.ExifIFD0Directory;
import com.drew.metadata.exif.ExifSubIFDDirectory;
import com.drew.metadata.heif.HeifDescriptor;
import com.drew.metadata.heif.HeifDirectory;
import org.ddr.image.ImageType;
import org.ddr.image.MetadataReader;

import java.awt.*;

public class HeifMetadataReader implements MetadataReader {
    @Override
    public boolean canRead(FileType type) {
        return type == FileType.Heif;
    }

    @Override
    public ImageType getType(Metadata metadata) {
        // FIXME read icc profile
        return ImageType.JPG;
    }

    /**
     * @see HeifDescriptor#getRotationDescription()
     * @see ExifDescriptorBase#getOrientationDescription()
     */
    @Override
    public Dimension getDimension(Metadata metadata) {
        Integer width = null;
        Integer height = null;
        Boolean rotated = null;
        for (Directory directory : metadata.getDirectories()) {
            if (directory instanceof HeifDirectory) {
                Integer r = directory.getInteger(HeifDirectory.TAG_IMAGE_ROTATION);
                if (r != null) {
                    rotated = r % 2 == 1;
                }
                // FIXME 实际上可以在此处获取到 width 和 height,但是图片会直接嵌入到 word 中,能否呈现因系统支持而异,特意不获取走格式转换流程
//                if (width == null) {
//                    width = directory.getInteger(HeifDirectory.TAG_IMAGE_WIDTH);
//                }
//                if (height == null) {
//                    height = directory.getInteger(HeifDirectory.TAG_IMAGE_HEIGHT);
//                }
            } else if (directory instanceof ExifIFD0Directory) {
                if (rotated == null) {
                    Integer r = directory.getInteger(ExifIFD0Directory.TAG_ORIENTATION);
                    if (r != null) {
                        rotated = r > 4;
                    }
                }
            } else if (directory instanceof ExifSubIFDDirectory) {
                if (width == null) {
                    width = directory.getInteger(ExifSubIFDDirectory.TAG_EXIF_IMAGE_WIDTH);
                }
                if (height == null) {
                    height = directory.getInteger(ExifSubIFDDirectory.TAG_EXIF_IMAGE_HEIGHT);
                }
            }
        }
        if (width != null && height != null) {
            if (Boolean.TRUE.equals(rotated)) {
                //noinspection SuspiciousNameCombination
                return new Dimension(height, width);
            }
            return new Dimension(width, height);
        }
        return null;
    }
}


================================================
FILE: src/main/java/org/ddr/image/heif/HeifProviderInfo.java
================================================
package org.ddr.image.heif;

import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;

final class HeifProviderInfo extends ReaderWriterProviderInfo {
    HeifProviderInfo() {
        super(
                HeifProviderInfo.class,
                new String[]{"heif", "HEIF"}, // Names
                new String[]{"heif", "heic"}, // Suffixes
                new String[]{"image/heif", "image/heic", "image/heif-sequence", "image/heic-sequence"}, // Mime-types
                "org.ddr.image.heif.HeifImageReader", // Reader class name
                new String[]{"org.ddr.image.heif.HeifImageReaderSpi"},
                null,
                null,
                false, null, null, null, null,
                true, null, null, null, null
        );
    }
}


================================================
FILE: src/main/java/org/ddr/image/jpeg/JpegMetadataReader.java
================================================
package org.ddr.image.jpeg;

import com.drew.imaging.FileType;
import com.drew.metadata.Directory;
import com.drew.metadata.Metadata;
import com.drew.metadata.jpeg.JpegDirectory;
import org.ddr.image.ImageType;
import org.ddr.image.MetadataReader;

import java.awt.*;

public class JpegMetadataReader implements MetadataReader {
    @Override
    public boolean canRead(FileType type) {
        return type == FileType.Jpeg;
    }

    @Override
    public ImageType getType(Metadata metadata) {
        return ImageType.JPG;
    }

    @Override
    public Dimension getDimension(Metadata metadata) {
        for (Directory directory : metadata.getDirectories()) {
            if (directory instanceof JpegDirectory) {
                Integer width = directory.getInteger(JpegDirectory.TAG_IMAGE_WIDTH);
                Integer height = directory.getInteger(JpegDirectory.TAG_IMAGE_HEIGHT);
                if (width != null && height != null) {
                    return new Dimension(width, height);
                }
            }
        }
        return null;
    }
}


================================================
FILE: src/main/java/org/ddr/image/png/PngMetadataReader.java
================================================
package org.ddr.image.png;

import com.drew.imaging.FileType;
import com.drew.imaging.png.PngChunkType;
import com.drew.metadata.Directory;
import com.drew.metadata.Metadata;
import com.drew.metadata.png.PngDirectory;
import org.ddr.image.ImageType;
import org.ddr.image.MetadataReader;

import java.awt.*;

public class PngMetadataReader implements MetadataReader {
    @Override
    public boolean canRead(FileType type) {
        return type == FileType.Png;
    }

    @Override
    public ImageType getType(Metadata metadata) {
        return ImageType.PNG;
    }

    @Override
    public Dimension getDimension(Metadata metadata) {
        for (Directory directory : metadata.getDirectories()) {
            if (directory instanceof PngDirectory) {
                if (((PngDirectory) directory).getPngChunkType() == PngChunkType.IHDR) {
                    Integer width = directory.getInteger(PngDirectory.TAG_IMAGE_WIDTH);
                    Integer height = directory.getInteger(PngDirectory.TAG_IMAGE_HEIGHT);
                    if (width != null && height != null) {
                        return new Dimension(width, height);
                    }
                }
            }
        }
        return null;
    }
}


================================================
FILE: src/main/java/org/ddr/image/tiff/TiffMetadataReader.java
================================================
package org.ddr.image.tiff;

import com.drew.imaging.FileType;
import com.drew.metadata.Directory;
import com.drew.metadata.Metadata;
import com.drew.metadata.exif.ExifIFD0Directory;
import org.ddr.image.ImageType;
import org.ddr.image.MetadataReader;

import java.awt.*;
import java.util.EnumSet;

public class TiffMetadataReader implements MetadataReader {
    private static final EnumSet<FileType> TIFF_TYPES = EnumSet.of(FileType.Tiff, FileType.Arw, FileType.Cr2, FileType.Nef, FileType.Orf, FileType.Rw2);

    @Override
    public boolean canRead(FileType type) {
        return TIFF_TYPES.contains(type);
    }

    @Override
    public ImageType getType(Metadata metadata) {
        return ImageType.TIFF;
    }

    @Override
    public Dimension getDimension(Metadata metadata) {
        for (Directory directory : metadata.getDirectories()) {
            if (directory instanceof ExifIFD0Directory) {
                Integer width = directory.getInteger(ExifIFD0Directory.TAG_IMAGE_WIDTH);
                Integer height = directory.getInteger(ExifIFD0Directory.TAG_IMAGE_HEIGHT);
                if (width != null && height != null) {
                    return new Dimension(width, height);
                }
            }
        }
        return null;
    }
}


================================================
FILE: src/main/java/org/ddr/image/webp/WebpMetadataReader.java
================================================
package org.ddr.image.webp;

import com.drew.imaging.FileType;
import com.drew.metadata.Directory;
import com.drew.metadata.Metadata;
import com.drew.metadata.webp.WebpDirectory;
import org.ddr.image.ImageType;
import org.ddr.image.MetadataReader;

import java.awt.*;

public class WebpMetadataReader implements MetadataReader{
    @Override
    public boolean canRead(FileType type) {
        return type == FileType.WebP;
    }

    @Override
    public ImageType getType(Metadata metadata) {
        for (Directory directory : metadata.getDirectories()) {
            if (directory instanceof WebpDirectory) {
                Boolean hasAlpha = directory.getBooleanObject(WebpDirectory.TAG_HAS_ALPHA);
                if (Boolean.TRUE.equals(hasAlpha)) {
                    return ImageType.PNG;
                }
            }
        }
        return ImageType.JPG;
    }

    @Override
    public Dimension getDimension(Metadata metadata) {
        for (Directory directory : metadata.getDirectories()) {
            if (directory instanceof WebpDirectory) {
                Integer width = directory.getInteger(WebpDirectory.TAG_IMAGE_WIDTH);
                Integer height = directory.getInteger(WebpDirectory.TAG_IMAGE_HEIGHT);
                if (width != null && height != null) {
                    return new Dimension(width, height);
                }
            }
        }
        return null;
    }
}


================================================
FILE: src/main/java/org/ddr/poi/html/ElementRenderer.java
================================================
/*
 * Copyright 2016 - 2021 Draco, https://github.com/draco1023
 *
 * 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
 *
 *     http://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.
 */

package org.ddr.poi.html;

import org.jsoup.nodes.Element;


/**
 * HTML元素渲染器
 *
 * @author Draco
 * @since 2021-02-08
 */
public interface ElementRenderer {
    /**
     * 开始渲染
     *
     * @param element HTML元素
     * @param context 渲染上下文
     * @return 是否继续渲染子元素
     */
    boolean renderStart(Element element, HtmlRenderContext context);

    /**
     * 元素渲染结束需要执行的逻辑
     *
     * @param element HTML元素
     * @param context 渲染上下文
     */
    default void renderEnd(Element element, HtmlRenderContext context) {
    }

    /**
     * @return 支持的HTML标签
     */
    String[] supportedTags();

    /**
     * @return 是否为块状渲染,如果为true在Word中会另起一个Paragraph
     */
    boolean renderAsBlock();
}


================================================
FILE: src/main/java/org/ddr/poi/html/ElementRendererProvider.java
================================================
/*
 * Copyright 2016 - 2021 Draco, https://github.com/draco1023
 *
 * 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
 *
 *     http://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.
 */

package org.ddr.poi.html;

/**
 * HTML元素渲染器提供者
 *
 * @author Draco
 * @since 2022-10-21
 */
@FunctionalInterface
public interface ElementRendererProvider {
    /**
     * 根据HTML元素名称获取渲染器
     *
     * @param tagNormalName HTML元素名称(小写)
     * @return HTML元素渲染器
     */
    ElementRenderer get(String tagNormalName);
}


================================================
FILE: src/main/java/org/ddr/poi/html/HtmlConstants.java
================================================
/*
 * Copyright 2016 - 2021 Draco, https://github.com/draco1023
 *
 * 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
 *
 *     http://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.
 */

package org.ddr.poi.html;

import org.apache.commons.compress.utils.Sets;

import java.util.Set;

/**
 * HTML常量
 *
 * @author Draco
 * @since 2021-02-23
 */
public interface HtmlConstants {
    String TAG_A = "a";
    String TAG_IMG = "img";
    String TAG_BR = "br";
    String TAG_MATH = "math";
    String TAG_HR = "hr";
    String TAG_OL = "ol";
    String TAG_UL = "ul";
    String TAG_LI = "li";
    String TAG_TABLE = "table";
    String TAG_S = "s";
    String TAG_DEL = "del";
    /**
     * HTML5不支持strike
     */
    String TAG_STRIKE = "strike";
    String TAG_I = "i";
    String TAG_EM = "em";
    String TAG_B = "b";
    String TAG_STRONG = "strong";
    String TAG_U = "u";
    String TAG_MARK = "mark";
    String TAG_SUB = "sub";
    String TAG_SUP = "sup";
    String TAG_H1 = "h1";
    String TAG_H2 = "h2";
    String TAG_H3 = "h3";
    String TAG_H4 = "h4";
    String TAG_H5 = "h5";
    String TAG_H6 = "h6";
    /**
     * HTML5不支持big
     */
    String TAG_BIG = "big";
    String TAG_SMALL = "small";
    String TAG_CAPTION = "caption";
    String TAG_COLGROUP = "colgroup";
    String TAG_COL = "col";
    String TAG_TR = "tr";
    String TAG_TH = "th";
    String TAG_TD = "td";
    String TAG_THEAD = "thead";
    String TAG_TBODY = "tbody";
    String TAG_TFOOT = "tfoot";

    String TAG_FRAME = "frame";
    String TAG_FRAMESET = "frameset";
    String TAG_IFRAME = "iframe";
    String TAG_NOFRAMES = "noframes";
    String TAG_HTML = "html";
    String TAG_HEAD = "head";
    String TAG_BODY = "body";
    String TAG_SCRIPT = "script";
    String TAG_NOSCRIPT = "noscript";
    String TAG_TEMPLATE = "template";

    String TAG_SVG = "svg";
    String TAG_RUBY = "ruby";
    String TAG_RP = "rp";
    String TAG_RT = "rt";
    String TAG_FIGURE = "figure";
    String TAG_FIGURE_CAPTION = "figcaption";
    String TAG_PRE = "pre";
    String TAG_XMP = "xmp";
    String TAG_LATEX = "latex";

    String ATTR_STYLE = "style";
    String ATTR_SRC = "src";
    String ATTR_WIDTH = "width";
    String ATTR_HEIGHT = "height";
    String ATTR_SPAN = "span";
    String ATTR_ROWSPAN = "rowspan";
    String ATTR_COLSPAN = "colspan";
    String ATTR_HREF = "href";
    String ATTR_TYPE = "type";
    String ATTR_FRAME = "frame";
    String ATTR_RULES = "rules";
    /**
     * 自定义属性:行索引
     */
    String ATTR_ROW_INDEX = "_r";
    /**
     * 自定义属性:列索引
     */
    String ATTR_COLUMN_INDEX = "_c";

    String CSS_BACKGROUND = "background";
    String CSS_BACKGROUND_COLOR = "background-color";
    String CSS_BORDER = "border";
    String CSS_BORDER_STYLE = "border-style";
    String CSS_BORDER_WIDTH = "border-width";
    String CSS_BORDER_COLOR = "border-color";
    String CSS_FONT = "font";
    String CSS_MARGIN = "margin";
    String CSS_MARGIN_TOP = "margin-top";
    String CSS_MARGIN_RIGHT = "margin-right";
    String CSS_MARGIN_BOTTOM = "margin-bottom";
    String CSS_MARGIN_LEFT = "margin-left";
    String CSS_PADDING = "padding";
    String CSS_PADDING_TOP = "padding-top";
    String CSS_PADDING_RIGHT = "padding-right";
    String CSS_PADDING_BOTTOM = "padding-bottom";
    String CSS_PADDING_LEFT = "padding-left";
    String CSS_FONT_STYLE = "font-style";
    String CSS_FONT_VARIANT_CAPS = "font-variant-caps";
    String CSS_FONT_WEIGHT = "font-weight";
    String CSS_FONT_SIZE = "font-size";
    String CSS_LINE_HEIGHT = "line-height";
    String CSS_FONT_FAMILY = "font-family";
    String CSS_TEXT_DECORATION = "text-decoration";
    String CSS_TEXT_DECORATION_LINE = "text-decoration-line";
    String CSS_TEXT_DECORATION_STYLE = "text-decoration-style";
    String CSS_TEXT_DECORATION_COLOR = "text-decoration-color";
    String CSS_TEXT_INDENT = "text-indent";
    String CSS_VERTICAL_ALIGN = "vertical-align";
    String CSS_VISIBILITY = "visibility";
    String CSS_DISPLAY = "display";
    String CSS_COLOR = "color";
    String CSS_WIDTH = ATTR_WIDTH;
    String CSS_MAX_WIDTH = "max-width";
    String CSS_HEIGHT = ATTR_HEIGHT;
    String CSS_MAX_HEIGHT = "max-height";
    String CSS_BORDER_TOP = "border-top";
    String CSS_BORDER_RIGHT = "border-right";
    String CSS_BORDER_BOTTOM = "border-bottom";
    String CSS_BORDER_LEFT = "border-left";
    String CSS_BORDER_TOP_STYLE = "border-top-style";
    String CSS_BORDER_RIGHT_STYLE = "border-right-style";
    String CSS_BORDER_BOTTOM_STYLE = "border-bottom-style";
    String CSS_BORDER_LEFT_STYLE = "border-left-style";
    String CSS_BORDER_TOP_WIDTH = "border-top-width";
    String CSS_BORDER_RIGHT_WIDTH = "border-right-width";
    String CSS_BORDER_BOTTOM_WIDTH = "border-bottom-width";
    String CSS_BORDER_LEFT_WIDTH = "border-left-width";
    String CSS_BORDER_TOP_COLOR = "border-top-color";
    String CSS_BORDER_RIGHT_COLOR = "border-right-color";
    String CSS_BORDER_BOTTOM_COLOR = "border-bottom-color";
    String CSS_BORDER_LEFT_COLOR = "border-left-color";
    String CSS_FLOAT = "float";
    String CSS_WHITE_SPACE = "white-space";
    String CSS_LIST_STYLE = "list-style";
    String CSS_LIST_STYLE_TYPE = "list-style-type";
    String CSS_LIST_STYLE_POSITION = "list-style-position";
    String CSS_BORDER_COLLAPSE = "border-collapse";
    String CSS_BORDER_SPACING = "border-spacing";
    String CSS_CAPTION_SIDE = "caption-side";
    String CSS_LETTER_SPACING = "letter-spacing";
    String CSS_TEXT_ALIGN = "text-align";

    String NORMAL = "normal";
    String ITALIC = "italic";
    String OBLIQUE = "oblique";
    String SMALL_CAPS = "small-caps";
    String BOLD = "bold";
    String BOLDER = "bolder";
    String LIGHTER = "lighter";
    String START = "start";
    String LEFT = "left";
    String END = "end";
    String RIGHT = "right";
    String CENTER = "center";
    String JUSTIFY = "justify";
    String JUSTIFY_ALL = "justify-all";
    String TOP = "top";
    String BOTTOM = "bottom";
    String MIDDLE = "middle";
    String AUTO = "auto";

    String XX_SMALL = "xx-small";
    String X_SMALL = "x-small";
    String SMALL = "small";
    String MEDIUM = "medium";
    String LARGE = "large";
    String X_LARGE = "x-large";
    String XX_LARGE = "xx-large";
    String XXX_LARGE = "xxx-large";

    String SMALLER = "smaller";
    String LARGER = "larger";

    String THIN = "thin";
    String THICK = "thick";

    String PT = "pt";
    String PC = "pc";
    String IN = "in";
    String CM = "cm";
    String MM = "mm";
    String PX = "px";
    String EM = "em";
    String REM = "rem";
    String VW = "vw";
    String VH = "vh";
    String VMIN = "vmin";
    String VMAX = "vmax";
    String PERCENT = "%";
    // 自定义单位
    String EMU = "emu";
    /**
     * dxa的单位,twentieth of a point = 1 / 20 pt
     */
    String TWIP = "twip";

    String SLASH = "/";
    String COMMA = ",";
    String COLON = ":";
    String SHARP = "#";
    String SEMICOLON = ";";
    String QUESTION = "?";
    String PLUS = "+";
    String MINUS = "-";
    String LEFT_PARENTHESIS = "(";

    String LINE_THROUGH = "line-through";
    String UNDERLINE = "underline";
    String SOLID = "solid";
    String DOUBLE = "double";
    String DOTTED = "dotted";
    String DASHED = "dashed";
    String WAVY = "wavy";
    String NONE = "none";

    String GROOVE = "groove";
    String RIDGE = "ridge";
    // 类似groove
    String INSET = "inset";
    // 类似ridge
    String OUTSET = "outset";

    String HIDDEN = "hidden";
    String COLLAPSE = "collapse";

    String SUPER = "super";
    String SUB = "sub";

    String NO_WRAP = "nowrap";
    String PRE = "pre";
    String PRE_WRAP = "pre-wrap";
    String PRE_LINE = "pre-line";
    String BREAK_SPACES = "break-spaces";

    String INSIDE = "inside";
    String OUTSIDE = "outside";

    String VOID = "void";
    String ABOVE = "above";
    String BELOW = "below";
    String H_SIDES = "hsides";
    String V_SIDES = "vsides";
    String LHS = "lhs";
    String RHS = "rhs";
    String BOX = "box";
    String BORDER = "border";

    String GROUPS = "groups";
    String ROWS = "rows";
    String COLS = "cols";
    String ALL = "all";

    Set<String> FONT_STYLES = Sets.newHashSet(NORMAL, ITALIC, OBLIQUE);
    Set<String> FONT_VARIANTS = Sets.newHashSet(NORMAL, SMALL_CAPS);
    Set<String> FONT_WEIGHTS = Sets.newHashSet(NORMAL, BOLD, BOLDER, LIGHTER);
    Set<String> BORDER_STYLES = Sets.newHashSet(NONE, HIDDEN, DOTTED, DASHED, SOLID, DOUBLE, GROOVE, RIDGE, INSET, OUTSET);
    // 不支持overline
    Set<String> TEXT_DECORATION_LINES = Sets.newHashSet(UNDERLINE, LINE_THROUGH);
    Set<String> TEXT_DECORATION_STYLES = Sets.newHashSet(SOLID, DOUBLE, DOTTED, DASHED, WAVY);
    Set<String> LIST_STYLE_POSITIONS = Sets.newHashSet(INSIDE, OUTSIDE);

    /**
     * 可继承的样式
     * <a href="https://www.w3.org/TR/CSS22/propidx.html">Specification</a>
     */
    Set<String> INHERITABLE_STYLES = Sets.newHashSet(
            "azimuth",
            CSS_BORDER_COLLAPSE,
            CSS_BORDER_SPACING,
            CSS_CAPTION_SIDE,
            CSS_COLOR,
            "cursor",
            "direction",
            "elevation",
            "empty-cells",
            CSS_FONT_FAMILY,
            CSS_FONT_SIZE,
            CSS_FONT_STYLE,
            CSS_FONT_VARIANT_CAPS,
            CSS_FONT_WEIGHT,
            CSS_FONT,
            CSS_LETTER_SPACING,
            CSS_LINE_HEIGHT,
            "list-style-image",
            "list-style-position",
            CSS_LIST_STYLE_TYPE,
            CSS_LIST_STYLE,
            "orphans",
            "pitch-range",
            "pitch",
            "quotes",
            "richness",
            "speak-header",
            "speak-numeral",
            "speak-punctuation",
            "speak",
            "speech-rate",
            "stress",
            CSS_TEXT_ALIGN,
            CSS_TEXT_INDENT,
            "text-transform",
            CSS_VISIBILITY,
            "voice-family",
            "volume",
            CSS_WHITE_SPACE,
            "widows",
            "word-spacing"
    );

    /**
     * 需要保留的空标签
     */
    Set<String> KEEP_EMPTY_TAGS = Sets.newHashSet(TAG_LI, TAG_HR);

    /**
     * Word中一些主要的默认字体
     */
    Set<String> MAJOR_FONT = Sets.newHashSet("宋体", "SIMSUN", "新細明體", "TIMES NEW ROMAN", "ARIAL");

    String DEFINED_ITALIC = inlineStyle(CSS_FONT_STYLE, ITALIC);
    String DEFINED_STRIKE = inlineStyle(CSS_TEXT_DECORATION_LINE, LINE_THROUGH);
    String DEFINED_BOLD = inlineStyle(CSS_FONT_WEIGHT, BOLD);
    String DEFINED_UNDERLINE = inlineStyle(CSS_TEXT_DECORATION_LINE, UNDERLINE);
    String DEFINED_SUPERSCRIPT = inlineStyle(CSS_VERTICAL_ALIGN, SUPER);
    String DEFINED_SUBSCRIPT = inlineStyle(CSS_VERTICAL_ALIGN, SUB);
    String DEFINED_LARGER = inlineStyle(CSS_FONT_SIZE, LARGER);
    String DEFINED_SMALLER = inlineStyle(CSS_FONT_SIZE, SMALLER);
    String DEFINED_PRE = inlineStyle(CSS_WHITE_SPACE, PRE);

    /**
     * 生成行内样式声明
     *
     * @param key 样式属性
     * @param value 样式值
     * @return 行内样式声明
     */
    static String inlineStyle(String key, String value) {
        return key + COLON + value + SEMICOLON;
    }

    /**
     * @param fontName 字体名称
     * @return 是否为主要字体
     */
    static boolean isMajorFont(String fontName) {
        return MAJOR_FONT.contains(fontName.toUpperCase());
    }
}


================================================
FILE: src/main/java/org/ddr/poi/html/HtmlRenderConfig.java
================================================
/*
 * Copyright 2016 - 2021 Draco, https://github.com/draco1023
 *
 * 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
 *
 *     http://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.
 */

package org.ddr.poi.html;

import org.ddr.poi.html.util.CSSLength;
import org.ddr.poi.math.MathRenderConfig;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STLevelSuffix;

import java.util.List;

/**
 * @author Draco
 * @since 2021-10-26
 */
public class HtmlRenderConfig {
    private String globalFont;

    private CSSLength globalFontSize;
    private int globalFontSizeInHalfPoints;

    private boolean showDefaultTableBorderInTableCell;
    private List<ElementRenderer> customRenderers;

    private int numberingIndent = -1;
    private int numberingHanging = -1;
    private STLevelSuffix.Enum numberingSpacing;

    private final MathRenderConfig mathRenderConfig = new MathRenderConfig();

    /**
     * @return global font family
     */
    public String getGlobalFont() {
        return globalFont;
    }

    public void setGlobalFont(String globalFont) {
        this.globalFont = globalFont;
    }

    /**
     * @return global font size
     */
    public CSSLength getGlobalFontSize() {
        return globalFontSize;
    }

    public void setGlobalFontSize(CSSLength globalFontSize) {
        this.globalFontSize = globalFontSize;
        globalFontSizeInHalfPoints = globalFontSize == null ? 0 : globalFontSize.toHalfPoints();
    }

    public int getGlobalFontSizeInHalfPoints() {
        return globalFontSizeInHalfPoints;
    }

    /**
     * @return whether to show default table borders if the table inside a table cell
     */
    public boolean isShowDefaultTableBorderInTableCell() {
        return showDefaultTableBorderInTableCell;
    }

    public void setShowDefaultTableBorderInTableCell(boolean showDefaultTableBorderInTableCell) {
        this.showDefaultTableBorderInTableCell = showDefaultTableBorderInTableCell;
    }

    /**
     * @return custom html tag renderers
     */
    public List<ElementRenderer> getCustomRenderers() {
        return customRenderers;
    }

    public void setCustomRenderers(List<ElementRenderer> customRenderers) {
        this.customRenderers = customRenderers;
    }

    /**
     * @return custom numbering indent
     */
    public int getNumberingIndent() {
        return numberingIndent;
    }

    public void setNumberingIndent(int numberingIndent) {
        this.numberingIndent = numberingIndent;
    }

    /**
     * @return custom numbering hanging
     */
    public int getNumberingHanging() {
        return numberingHanging;
    }

    public void setNumberingHanging(int numberingHanging) {
        this.numberingHanging = numberingHanging;
    }

    /**
     * @return custom numbering spacing
     */
    public STLevelSuffix.Enum getNumberingSpacing() {
        return numberingSpacing;
    }

    public void setNumberingSpacing(STLevelSuffix.Enum numberingSpacing) {
        this.numberingSpacing = numberingSpacing;
    }

    public MathRenderConfig getMathRenderConfig() {
        return mathRenderConfig;
    }
}


================================================
FILE: src/main/java/org/ddr/poi/html/HtmlRenderContext.java
================================================
/*
 * Copyright 2016 - 2021 Draco, https://github.com/draco1023
 *
 * 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
 *
 *     http://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.
 */

package org.ddr.poi.html;

import com.deepoove.poi.render.RenderContext;
import com.steadystate.css.dom.CSSStyleDeclarationImpl;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.util.Units;
import org.apache.poi.xwpf.usermodel.BodyType;
import org.apache.poi.xwpf.usermodel.IBody;
import org.apache.poi.xwpf.usermodel.IRunBody;
import org.apache.poi.xwpf.usermodel.SVGPictureData;
import org.apache.poi.xwpf.usermodel.SVGRelation;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFFooter;
import org.apache.poi.xwpf.usermodel.XWPFFootnote;
import org.apache.poi.xwpf.usermodel.XWPFHeader;
import org.apache.poi.xwpf.usermodel.XWPFHyperlinkRun;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFPicture;
import org.apache.poi.xwpf.usermodel.XWPFRelation;
import org.apache.poi.xwpf.usermodel.XWPFRun;
import org.apache.poi.xwpf.usermodel.XWPFStyle;
import org.apache.poi.xwpf.usermodel.XWPFStyles;
import org.apache.poi.xwpf.usermodel.XWPFTable;
import org.apache.poi.xwpf.usermodel.XWPFTableCell;
import org.apache.xmlbeans.XmlCursor;
import org.apache.xmlbeans.XmlObject;
import org.apache.xmlbeans.impl.xb.xmlschema.SpaceAttribute;
import org.ddr.poi.html.util.CSSLength;
import org.ddr.poi.html.util.CSSLengthUnit;
import org.ddr.poi.html.util.CSSStyleUtils;
import org.ddr.poi.html.util.Colors;
import org.ddr.poi.html.util.InlineStyle;
import org.ddr.poi.html.util.NamedFontSize;
import org.ddr.poi.html.util.NumberingContext;
import org.ddr.poi.html.util.RenderUtils;
import org.ddr.poi.html.util.WhiteSpaceRule;
import org.ddr.poi.html.util.XWPFParagraphRuns;
import org.ddr.poi.math.MathMLUtils;
import org.ddr.poi.math.MathRenderConfig;
import org.ddr.poi.util.XmlUtils;
import org.jsoup.internal.StringUtil;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.nodes.Node;
import org.jsoup.nodes.TextNode;
import org.openxmlformats.schemas.drawingml.x2006.main.CTBlip;
import org.openxmlformats.schemas.drawingml.x2006.main.CTGraphicalObjectFrameLocking;
import org.openxmlformats.schemas.drawingml.x2006.main.CTNonVisualGraphicFrameProperties;
import org.openxmlformats.schemas.drawingml.x2006.main.CTOfficeArtExtension;
import org.openxmlformats.schemas.drawingml.x2006.main.CTOfficeArtExtensionList;
import org.openxmlformats.schemas.drawingml.x2006.picture.CTPicture;
import org.openxmlformats.schemas.drawingml.x2006.wordprocessingDrawing.CTAnchor;
import org.openxmlformats.schemas.drawingml.x2006.wordprocessingDrawing.CTInline;
import org.openxmlformats.schemas.drawingml.x2006.wordprocessingDrawing.CTPosH;
import org.openxmlformats.schemas.drawingml.x2006.wordprocessingDrawing.CTPosV;
import org.openxmlformats.schemas.drawingml.x2006.wordprocessingDrawing.STAlignH;
import org.openxmlformats.schemas.drawingml.x2006.wordprocessingDrawing.STAlignV;
import org.openxmlformats.schemas.drawingml.x2006.wordprocessingDrawing.STRelFromH;
import org.openxmlformats.schemas.drawingml.x2006.wordprocessingDrawing.STRelFromV;
import org.openxmlformats.schemas.drawingml.x2006.wordprocessingDrawing.STWrapText;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTColor;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTDrawing;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTFonts;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTHyperlink;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTMarkupRange;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTP;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTPageMar;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTPageSz;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTR;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTRPr;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTSectPr;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTShd;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTStyle;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTbl;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTblBorders;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTText;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTUnderline;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STBorder;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STOnOff;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STShd;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STStyleType;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STThemeColor;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STUnderline;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STVerticalAlignRun;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.xml.namespace.QName;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.net.URI;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

/**
 * HTML字符串渲染上下文
 *
 * @author Draco
 * @since 2021-02-08
 */
public class HtmlRenderContext extends RenderContext<String> {
    private static final Logger log = LoggerFactory.getLogger(HtmlRenderContext.class);


    /**
     * 默认字号 小四 12pt 16px
     */
    private static final CSSLength DEFAULT_FONT_SIZE = new CSSLength(12, CSSLengthUnit.PT);
    /**
     * 默认超链接颜色
     */
    private static final String DEFAULT_HYPERLINK_COLOR = "0563C1";

    /**
     * HTML元素渲染器提供者
     */
    private final ElementRendererProvider rendererProvider;

    /**
     * 父容器(子元素通常为段落/表格)栈,主要用于渲染HTML表格时父容器的切换
     */
    private final LinkedList<IBody> ancestors = new LinkedList<>();

    /**
     * 行内样式栈,最近声明的样式最先生效
     */
    private final LinkedList<InlineStyle> inlineStyles = new LinkedList<>();

    /**
     * 字号栈,一些相对大小的字号值将被进行换算
     */
    private final LinkedList<Integer> fontSizesInHalfPoints = new LinkedList<>();

    /**
     * 列表上下文,用于处理嵌套列表
     */
    private final NumberingContext numberingContext;
    /**
     * 默认字号
     */
    private final CSSLength defaultFontSize;
    /**
     * 页面宽度
     */
    private final CSSLength pageWidth;
    /**
     * 页面高度
     */
    private final CSSLength pageHeight;
    /**
     * 页面顶部边距
     */
    private final CSSLength marginTop;
    /**
     * 页面右侧边距
     */
    private final CSSLength marginRight;
    /**
     * 页面底部边距
     */
    private final CSSLength marginBottom;
    /**
     * 页面左侧边距
     */
    private final CSSLength marginLeft;
    /**
     * 可用页面宽度
     */
    private final int availablePageWidth;
    /**
     * 可用页面高度
     */
    private final int availablePageHeight;
    /**
     * 占位符所在段落的样式ID
     */
    private String placeholderStyleId;

    /**
     * 当前Run元素,可能为超链接
     */
    private XWPFRun currentRun;

    /**
     * 全局字体,声明后所有样式中的字体将失效
     */
    private String globalFont;
    /**
     * 全局字号,声明后所有样式中的字号将失效
     */
    private BigInteger globalFontSize;
    /**
     * 嵌套表格是否默认显示边框
     */
    private boolean showDefaultTableBorderInTableCell;
    /**
     * 数学公式渲染配置
     */
    private MathRenderConfig mathRenderConfig;

    /**
     * 块状元素深度计数器
     */
    private int blockLevel;

    /**
     * 全局xml指针,保证其总是处于将要插入内容的位置,仅在需要移动之前进行push,适时pop还原位置
     */
    private final XmlCursor globalCursor;

    /**
     * 防重段落
     */
    private XWPFParagraph dedupeParagraph;

    /**
     * 同一段落内的前一个文本节点
     */
    private TextWrapper previousText;

    /**
     * 前一个图片所在节点
     */
    private CTR previousDrawingRun;

    /**
     * 构造方法
     *
     * @param context 原始渲染上下文
     */
    public HtmlRenderContext(RenderContext<String> context, ElementRendererProvider rendererProvider) {
        super(context.getEleTemplate(), context.getData(), context.getTemplate());
        this.rendererProvider = rendererProvider;
        globalCursor = getRun().getCTR().newCursor();

        numberingContext = new NumberingContext(getXWPFDocument());

        int w = RenderUtils.A4_WIDTH;
        int h = RenderUtils.A4_HEIGHT;
        int top = RenderUtils.DEFAULT_TOP_MARGIN;
        int right = RenderUtils.DEFAULT_RIGHT_MARGIN;
        int bottom = RenderUtils.DEFAULT_BOTTOM_MARGIN;
        int left = RenderUtils.DEFAULT_LEFT_MARGIN;
        CTSectPr sectPr = getXWPFDocument().getDocument().getBody().getSectPr();
        if (sectPr != null) {
            CTPageSz pgSz = sectPr.getPgSz();
            // 页面尺寸单位是twip
            if (pgSz != null) {
                w = pgSz.getW().intValue();
                h = pgSz.getH().intValue();
            }

            CTPageMar pgMar = sectPr.getPgMar();
            if (pgMar != null) {
                top = pgMar.getTop().intValue();
                right = pgMar.getRight().intValue();
                bottom = pgMar.getBottom().intValue();
                left = pgMar.getLeft().intValue();
            }
        }

        pageWidth = new CSSLength(w, CSSLengthUnit.TWIP);
        pageHeight = new CSSLength(h, CSSLengthUnit.TWIP);
        marginTop = new CSSLength(top, CSSLengthUnit.TWIP);
        marginRight = new CSSLength(right, CSSLengthUnit.TWIP);
        marginBottom = new CSSLength(bottom, CSSLengthUnit.TWIP);
        marginLeft = new CSSLength(left, CSSLengthUnit.TWIP);
        availablePageWidth = new CSSLength(w - left - right, CSSLengthUnit.TWIP).toEMU();
        availablePageHeight = new CSSLength(h - top - bottom, CSSLengthUnit.TWIP).toEMU();

        int fontSize = getXWPFDocument().getStyles().getDefaultRunStyle().getFontSize();
        defaultFontSize = fontSize > 0 ? new CSSLength(fontSize, CSSLengthUnit.PT) : DEFAULT_FONT_SIZE;

        extractPlaceholderStyle();
    }

    /**
     * 抽取占位符所在段落的样式
     */
    private void extractPlaceholderStyle() {
        XWPFRun run = getRun();
        IRunBody runParent = run.getParent();
        if (runParent instanceof XWPFParagraph) {
            XWPFParagraph paragraph = (XWPFParagraph) runParent;
            String styleId = paragraph.getStyleID();
            boolean existsRPr = run.getCTR().isSetRPr();

            if (styleId == null && !existsRPr) {
                return;
            } else if (styleId != null && !existsRPr) {
                placeholderStyleId = styleId;
                return;
            }

            XWPFStyles styles = getXWPFDocument().getStyles();
            CTStyle newCTStyle = CTStyle.Factory.newInstance();
            newCTStyle.setCustomStyle(STOnOff.TRUE);
            newCTStyle.setType(STStyleType.PARAGRAPH);
            newCTStyle.addNewHidden();
            newCTStyle.setRPr(run.getCTR().getRPr());
            XmlUtils.removeNamespaces(newCTStyle.getRPr());

            String newStyleId = styleId + styles.getNumberOfStyles();
            newCTStyle.setStyleId(newStyleId);
            newCTStyle.addNewName().setVal(newStyleId);
            placeholderStyleId = newStyleId;

            if (styleId != null) {
                newCTStyle.addNewBasedOn().setVal(styleId);
            }

            XWPFStyle newStyle = new XWPFStyle(newCTStyle, styles);
            styles.addStyle(newStyle);

            paragraph.setStyle(newStyleId);
        }
    }

    @Override
    public IBody getContainer() {
        IBody container = ancestors.peek();
        return container == null ? super.getContainer() : container;
    }

    /**
     * 父容器入栈
     *
     * @param body 父容器
     */
    public void pushContainer(IBody body) {
        ancestors.push(body);
    }

    /**
     * 父容器出栈
     */
    public void popContainer() {
        ancestors.pop();
    }

    /**
     * 获取最近的段落,如果当前最近位置的内容元素是表格,则创建一个与之平级的段落
     *
     * @return 最近的段落
     */
    public XWPFParagraph getClosestParagraph() {
        if (globalCursor.getObject() == getRun().getCTR()) {
            return (XWPFParagraph) getRun().getParent();
        }

        globalCursor.push();
        XWPFParagraph paragraph = null;
        if (globalCursor.toPrevSibling()) {
            XmlObject object = globalCursor.getObject();
            if (object instanceof CTP) {
                paragraph = getContainer().getParagraph((CTP) object);
            } else {
                // pop() is safer than toNextSibling()
                globalCursor.pop();
                globalCursor.push();
                paragraph = newParagraph(null, globalCursor);
                RenderUtils.paragraphStyle(this, paragraph, CSSStyleUtils.EMPTY_STYLE);
            }
        }
        globalCursor.pop();

        if (paragraph != null) {
            return paragraph;
        }

        throw new IllegalStateException("No paragraph in stack");
    }

    /**
     * 开始渲染超链接
     *
     * @param uri 链接地址
     */
    public void startHyperlink(String uri) {
        try {
            URI.create(uri);
        } catch (Exception e) {
            log.warn("Illegal href", e);
            uri = "#";
        }
        if (isBlocked()) {
            XWPFParagraph paragraph = getClosestParagraph();
            currentRun = paragraph.createHyperlinkRun(uri);
            if (dedupeParagraph == paragraph) {
                unmarkDedupe();
            }
        } else {
            // 在占位符之前插入超链接
            String rId = getRun().getParent().getPart().getPackagePart()
                    .addExternalRelationship(uri, XWPFRelation.HYPERLINK.getRelation()).getId();
            XmlCursor xmlCursor = getRun().getCTR().newCursor();
            xmlCursor.insertElement(XmlUtils.HYPERLINK_QNAME);
            xmlCursor.toPrevSibling();
            CTHyperlink ctHyperlink = (CTHyperlink) xmlCursor.getObject();
            xmlCursor.dispose();
            ctHyperlink.setId(rId);
            ctHyperlink.addNewR();
            currentRun = new XWPFHyperlinkRun(ctHyperlink, ctHyperlink.getRArray(0), getRun().getParent());
        }
    }

    /**
     * 结束渲染超链接
     */
    public void endHyperlink() {
        currentRun = null;
    }

    /**
     * 新建段落
     *
     * @param container 容器
     * @param cursor xml指针
     * @return 段落
     */
    public XWPFParagraph newParagraph(IBody container, XmlCursor cursor) {
        if (container == null) {
            container = getContainer();
        }
        XWPFParagraph xwpfParagraph = container.insertNewParagraph(cursor);
        if (placeholderStyleId != null) {
            xwpfParagraph.setStyle(placeholderStyleId);
        }
        markDedupe(xwpfParagraph);
        previousText = null;
        adjustPicture();
        return xwpfParagraph;
    }

    private void adjustPicture() {
        if (previousDrawingRun != null) {
            XmlCursor xmlCursor = previousDrawingRun.newCursor();
            List<CTDrawing> drawings = new ArrayList<>(previousDrawingRun.getDrawingList());
            boolean hasText = false;
            while (xmlCursor.toPrevSibling()) {
                if (XmlUtils.R_QNAME.equals(xmlCursor.getName())) {
                    CTR ctr = (CTR) xmlCursor.getObject();
                    for (int i = 0, l = ctr.sizeOfTArray(); i < l; i++) {
                        CTText ctText = ctr.getTArray(i);
                        if (StringUtils.isNotBlank(ctText.getStringValue())) {
                            hasText = true;
                            break;
                        }
                    }
                    for (int i = 0, l = ctr.sizeOfDrawingArray(); i < l; i++) {
                        drawings.add(ctr.getDrawingArray(i));
                    }
                } else if (MathMLUtils.OMATH_QNAME.equals(xmlCursor.getName())) {
                    hasText = true;
                }
            }
            if (!hasText) {
                for (CTDrawing drawing : drawings) {
                    CTAnchor ctAnchor = RenderUtils.inlineToAnchor(drawing);
                    ctAnchor.addNewWrapTopAndBottom();

                    CTPosH ctPosH = ctAnchor.addNewPositionH();
                    ctPosH.setRelativeFrom(STRelFromH.MARGIN);
                    ctPosH.setAlign(STAlignH.LEFT);

                    CTPosV ctPosV = ctAnchor.addNewPositionV();
                    ctPosV.setRelativeFrom(STRelFromV.PARAGRAPH);
                    ctPosV.setAlign(STAlignV.TOP);
                }
            }
            xmlCursor.dispose();
            previousDrawingRun = null;
        }
    }

    /**
     * 新建CTR
     *
     * @return CTR
     */
    public CTR newRun() {
        // 超链接虽然不是段落,但是内部可以容纳多个run
        if (currentRun instanceof XWPFHyperlinkRun) {
            XmlCursor xmlCursor = currentRun.getCTR().newCursor();
            CTR ctr;
            if (xmlCursor.toFirstChild()) {
                ctr = ((XWPFHyperlinkRun) currentRun).getCTHyperlink().addNewR();
            } else {
                // run没有内容则直接复用
                ctr = currentRun.getCTR();
            }
            xmlCursor.dispose();
            // 默认链接样式
            initHyperlinkStyle(ctr);

            return ctr;
        }
        // 考虑到样式可能不一致,总是创建新的run
        if (isBlocked()) {
            XWPFParagraph paragraph = getClosestParagraph();
            currentRun = paragraph.createRun();
            if (dedupeParagraph == paragraph) {
                unmarkDedupe();
            }
        } else {
            // 在占位符之前插入run
            XmlCursor xmlCursor = getRun().getCTR().newCursor();
            xmlCursor.insertElement(XmlUtils.R_QNAME);
            xmlCursor.toPrevSibling();
            CTR ctr = (CTR) xmlCursor.getObject();
            xmlCursor.dispose();
            currentRun = new XWPFRun(ctr, getRun().getParent());
        }
        return currentRun.getCTR();
    }

    /**
     * 初始化超链接样式
     *
     * @param ctr CTR
     */
    private void initHyperlinkStyle(CTR ctr) {
        CTRPr rPr = RenderUtils.getRPr(ctr);
        CTColor ctColor = rPr.addNewColor();
        ctColor.setVal(DEFAULT_HYPERLINK_COLOR);
        ctColor.setThemeColor(STThemeColor.HYPERLINK);

        rPr.addNewU().setVal(STUnderline.SINGLE);
    }

    /**
     * 获取最近的表格,仅可在渲染表格及其内部元素的时候使用
     *
     * @return 最近的表格
     */
    public XWPFTable getClosestTable() {
        globalCursor.push();
        XWPFTable table = null;
        if (globalCursor.toPrevSibling()) {
            XmlObject object = globalCursor.getObject();
            if (object instanceof CTTbl) {
                table = getContainer().getTable((CTTbl) object);
            }
        }
        globalCursor.pop();

        if (table != null) {
            return table;
        }

        throw new IllegalStateException("No table in stack");
    }

    /**
     * 行内样式入栈
     *
     * @param inlineStyle 样式声明
     * @param block 是否为块状元素
     */
    public void pushInlineStyle(CSSStyleDeclarationImpl inlineStyle, boolean block) {
        String newFontSize = inlineStyle.getFontSize();
        // 默认值表示未声明字号
        int fontSize = Integer.MIN_VALUE;
        if (StringUtils.isNotBlank(newFontSize)) {
            NamedFontSize namedFontSize = NamedFontSize.of(newFontSize);
            if (namedFontSize != null) {
                // 固定名称的字号
                fontSize = namedFontSize.getSize().toHalfPoints();
            } else if (HtmlConstants.SMALLER.equalsIgnoreCase(newFontSize)) {
                // 相对小一号
                int inheritedFontSize = getInheritedFontSizeInHalfPoints();
                fontSize = RenderUtils.smallerFontSizeInHalfPoints(inheritedFontSize);
            } else if (HtmlConstants.LARGER.equalsIgnoreCase(newFontSize)) {
                // 相对大一号
                int inheritedFontSize = getInheritedFontSizeInHalfPoints();
                fontSize = RenderUtils.largerFontSizeInHalfPoints(inheritedFontSize);
            } else {
                CSSLength cssLength = CSSLength.of(newFontSize);
                if (cssLength.isValid()) {
                    if (cssLength.getUnit() == CSSLengthUnit.PERCENT) {
                        fontSize = (int) Math.rint(getInheritedFontSizeInHalfPoints()
                                * cssLength.getValue() * cssLength.getUnit().absoluteFactor());
                    } else {
                        int emu = lengthToEMU(cssLength);
                        fontSize = emu * 2 / Units.EMU_PER_POINT;
                    }
                }
            }
        }
        fontSizesInHalfPoints.push(fontSize);

        // text-decoration-line 在继承时需要合并
        String textDecorationLine = inlineStyle.getPropertyValue(HtmlConstants.CSS_TEXT_DECORATION_LINE);
        if (StringUtils.isNotBlank(textDecorationLine) && !HtmlConstants.NONE.equals(textDecorationLine)) {
            Set<String> remainValues = new HashSet<>(HtmlConstants.TEXT_DECORATION_LINES);
            String[] values = StringUtils.split(textDecorationLine, ' ');
            for (String value : values) {
                remainValues.remove(value);
            }

            if (!remainValues.isEmpty()) {
                StringBuilder lines = new StringBuilder(textDecorationLine);
                for (InlineStyle inheritedStyle : inlineStyles) {
                    String s = inheritedStyle.getDeclaration().getPropertyValue(HtmlConstants.CSS_TEXT_DECORATION_LINE);
                    if (HtmlConstants.NONE.equals(s)) {
                        break;
                    } else if (remainValues.contains(s)) {
                        lines.append(' ').append(s);
                        remainValues.remove(s);
                        if (remainValues.isEmpty()) {
                            break;
                        }
                    }
                }
                if (lines.length() > textDecorationLine.length()) {
                    inlineStyle.setProperty(HtmlConstants.CSS_TEXT_DECORATION_LINE, lines.toString(), null);
                }
            }
        }

        inlineStyles.push(new InlineStyle(inlineStyle, block));
    }

    /**
     * 行内样式出栈
     */
    public void popInlineStyle() {
        fontSizesInHalfPoints.pop();
        inlineStyles.pop();
    }

    /**
     * 当前元素的样式声明,在HTML元素渲染开始时立即调用才可得到正确的声明,因为在解析的过程中可能会动态插入样式
     *
     * @return 当前元素的样式声明
     */
    public CSSStyleDeclarationImpl currentElementStyle() {
        InlineStyle inlineStyle = inlineStyles.peek();
        return inlineStyle == null ? CSSStyleUtils.EMPTY_STYLE : inlineStyle.getDeclaration();
    }

    /**
     * 获取样式值,将被转换为小写
     *
     * @param property 样式名称
     * @return 样式值,未声明时返回空字符串
     */
    public String getPropertyValue(String property) {
        return getPropertyValue(property, false);
    }

    /**
     * 获取样式值,将被转换为小写
     *
     * @param property 样式名称
     * @param inlineOnly 是否仅获取行内元素样式
     * @return 样式值,未声明时返回空字符串
     */
    public String getPropertyValue(String property, boolean inlineOnly) {
        return getPropertyValue(property, false, inlineOnly);
    }

    /**
     * 获取样式值
     *
     * @param property 样式名称
     * @param caseSensitive 是否大小写无关,如果无关则将转换为小写,否则保留原始值
     * @param inlineOnly 是否仅获取行内元素样式
     * @return 样式值,未声明时返回空字符串
     */
    public String getPropertyValue(String property, boolean caseSensitive, boolean inlineOnly) {
        for (InlineStyle inlineStyle : inlineStyles) {
            if (inlineOnly && inlineStyle.isBlock()) {
                break;
            }
            String propertyValue = inlineStyle.getDeclaration().getPropertyValue(property);
            if (StringUtils.isNotBlank(propertyValue)) {
                return caseSensitive ? propertyValue : propertyValue.toLowerCase();
            }
        }
        return "";
    }

    /**
     * @return Word中设置的默认字号
     */
    public CSSLength getDefaultFontSize() {
        return defaultFontSize;
    }

    /**
     * @return 获取当前元素继承的字号,以“半点”为单位
     */
    public int getInheritedFontSizeInHalfPoints() {
        for (Integer fontSize : fontSizesInHalfPoints) {
            if (fontSize > 0) {
                return fontSize;
            }
        }
        return defaultFontSize.toHalfPoints();
    }

    /**
     * @return 父容器的可用宽度,以EMU为单位
     */
    public int getAvailableWidthInEMU() {
        IBody container = getContainer();
        if (container.getPartType() == BodyType.DOCUMENT) {
            return availablePageWidth;
        } else {
            return RenderUtils.getAvailableWidthInEMU(container);
        }
    }

    /**
     * 考虑约束计算长度,以EMU为单位
     *
     * @param length 长度声明
     * @param maxLength 最大长度声明
     * @param naturalEMU 原始长度
     * @param parentEMU 父容器长度
     * @return 以EMU为单位的长度值
     */
    public int computeLengthInEMU(String length, String maxLength, int naturalEMU, int parentEMU) {
        int emu = naturalEMU;

        if (length.length() > 0) {
            CSSLength cssLength = CSSLength.of(length);
            if (cssLength.isValid()) {
                emu = computeLengthInEMU(cssLength, naturalEMU, parentEMU);
            }
        }

        if (maxLength.length() > 0) {
            CSSLength cssLength = CSSLength.of(maxLength);
            if (cssLength.isValid()) {
                int maxEMU = computeLengthInEMU(cssLength, naturalEMU, parentEMU);
                emu = Math.min(maxEMU, emu);
            }
        }

        return Math.min(emu, parentEMU);
    }

    /**
     * 考虑约束计算长度,以EMU为单位
     *
     * @param cssLength 长度声明
     * @param naturalEMU 原始长度
     * @param parentEMU 父容器长度
     * @return 以EMU为单位的长度值
     */
    public int computeLengthInEMU(CSSLength cssLength, int naturalEMU, int parentEMU) {
        int length;
        if (cssLength.getUnit() == CSSLengthUnit.PERCENT) {
            if (parentEMU != Integer.MAX_VALUE) {
                length = (int) (parentEMU * cssLength.getValue() * cssLength.getUnit().absoluteFactor());
            } else {
                length = naturalEMU;
            }
        } else {
            length = lengthToEMU(cssLength);
        }
        return length;
    }

    /**
     * 渲染文本
     *
     * @param text 文本
     */
    public void renderText(String text) {
        String whiteSpace = getPropertyValue(HtmlConstants.CSS_WHITE_SPACE);
        WhiteSpaceRule rule = WhiteSpaceRule.of(whiteSpace, WhiteSpaceRule.NORMAL);

        StringBuilder sb = StringUtil.borrowBuilder();
        boolean mergeWhitespace = false;

        // https://en.wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references
        int len = text.length();
        int c;

        if (!rule.isKeepTrailingSpace()) {
            boolean reachedLastNonWhite = false;
            for (int i = len - 1; i >= 0; i -= Character.charCount(c)) {
                c = text.codePointAt(i);
                switch (c) {
                    case ' ':
                    case '\r':
                    case '\n':
                    case '\t':
                    case 173: // soft hyphen
                    case 8203: // zero width space
                    case 8204: // zero width non-joiner
                    case 8205: // zero width joiner
                    case 8206: // lrm
                    case 8207: // rlm
                    case 8288: // word joiner
                    case 8289: // apply function
                    case 8290: // invisible times
                    case 8291: // invisible separator
                        len = i;
                        break;
                    default:
                        if (Character.getType(c) == 16) {
                            len = i;
                        } else {
                            reachedLastNonWhite = true;
                        }
                        break;
                }
                if (reachedLastNonWhite) {
                    break;
                }
            }
        }
        if (len == 0) {
            return;
        }
        boolean endTrimmed = len < text.length();
        CTR ctr = newRun();

        for (int i = 0; i < len; i += Character.charCount(c)) {
            c = text.codePointAt(i);
            switch (c) {
                case '\r':
                    if (i + 1 < len && text.codePointAt(i + 1) == '\n') {
                        continue;
                    }
                    if (rule.isKeepLineBreak()) {
                        addText(ctr, sb, false);
                        ctr.addNewCr();
                    } else {
                        mergeWhitespace = true;
                    }
                    break;
                case '\n':
                    if (rule.isKeepLineBreak()) {
                        addText(ctr, sb, false);
                        ctr.addNewBr();
                    } else {
                        mergeWhitespace = true;
                    }
                    break;
                case ' ':
                    if (rule.isKeepSpaceAndTab()) {
                        sb.appendCodePoint(c);
                    } else {
                        mergeWhitespace = true;
                    }
                    break;
                case '\t':
                    if (rule.isKeepSpaceAndTab()) {
                        addText(ctr, sb, false);
                        ctr.addNewTab();
                    } else {
                        mergeWhitespace = true;
                    }
                    break;
                case 160: // nbsp
                case 8192: // enquad
                case 8193: // emquad
                case 8194: // ensp
                case 8195: // emsp
                case 8196: // emsp13
                case 8197: // emsp14
                case 8199: // numsp
                case 8200: // puncsp
                case 8201: // thinsp
                case 8202: // hairsp
                case 8239: // narrow space
                case 8287: // medium space
                    if (mergeWhitespace) {
                        sb.append(' ');
                        mergeWhitespace = false;
                    }
                    sb.append(' ');
                    break;
                case 173: // soft hyphen
                case 8203: // zero width space
                case 8204: // zero width non-joiner
                case 8205: // zero width joiner
                case 8206: // lrm
                case 8207: // rlm
                case 8288: // word joiner
                case 8289: // apply function
                case 8290: // invisible times
                case 8291: // invisible separator
                    continue;
                default:
                    if (Character.getType(c) == 16) {
                        continue;
                    }
                    if (mergeWhitespace) {
                        if (sb.length() > 0) {
                            sb.append(' ');
                        } else if (previousText != null && !previousText.isEndTrimmed()) {
                            sb.append(' ');
                            previousText = null;
                        }
                        mergeWhitespace = false;
                    }
                    sb.appendCodePoint(c);
                    if (previousText != null && previousText.isEndTrimmed()) {
                        CTText previous = previousText.getText();
                        previous.setStringValue(previous.getStringValue() + ' ');
                        previous.setSpace(SpaceAttribute.Space.PRESERVE);
                        previousText = null;
                    }
                    break;
            }
        }

        addText(ctr, sb, endTrimmed);
        StringUtil.releaseBuilder(sb);

        // 应用样式
        applyTextStyle(ctr);

        if (!(currentRun instanceof XWPFHyperlinkRun)) {
            currentRun = null;
        }
    }

    private void addText(CTR ctr, StringBuilder sb, boolean endTrimmed) {
        if (sb.length() > 0) {
            CTText ctText = ctr.addNewT();
            String text = sb.toString();
            ctText.setStringValue(text);
            if (text.charAt(0) == ' ' || text.charAt(sb.length() - 1) == ' ') {
                ctText.setSpace(SpaceAttribute.Space.PRESERVE);
            }
            sb.delete(0, sb.length());
            previousText = new TextWrapper(ctText, endTrimmed);
        }
    }

    /**
     * 应用文本样式
     *
     * @param ctr CTR
     */
    private void applyTextStyle(CTR ctr) {
        CTRPr rPr = RenderUtils.getRPr(ctr);

        // 字体,如果声明了全局字体则忽略样式声明
        String fontFamily = StringUtils.isBlank(globalFont) ? getPropertyValue(HtmlConstants.CSS_FONT_FAMILY) : globalFont;
        if (StringUtils.isNotBlank(fontFamily)) {
            CTFonts ctFonts = rPr.addNewRFonts();
            // ASCII
            ctFonts.setAscii(fontFamily);
            // High ANSI
            ctFonts.setHAnsi(fontFamily);
            // Complex Script
            ctFonts.setCs(fontFamily);
            // East Asian
            ctFonts.setEastAsia(fontFamily);
        }

        // 字号
        if (globalFontSize == null) {
            String fontSize = getPropertyValue(HtmlConstants.CSS_FONT_SIZE);
            if (StringUtils.isNotBlank(fontSize)) {
                int sz = getInheritedFontSizeInHalfPoints();
                rPr.addNewSz().setVal(BigInteger.valueOf(sz));
            }
        } else {
            // 如果定义了全局字号则忽略样式声明
            rPr.addNewSz().setVal(globalFontSize);
        }

        // 加粗
        String fontWeight = getPropertyValue(HtmlConstants.CSS_FONT_WEIGHT);
        if (fontWeight.contains(HtmlConstants.BOLD)) {
            rPr.addNewB();
        } else if (NumberUtils.isParsable(fontWeight) && Float.parseFloat(fontWeight) > 500) {
            rPr.addNewB();
        }

        // 斜体
        String fontStyle = getPropertyValue(HtmlConstants.CSS_FONT_STYLE);
        if (HtmlConstants.ITALIC.equals(fontStyle) || HtmlConstants.OBLIQUE.equals(fontStyle)) {
            rPr.addNewI();
        }

        // 颜色
        String color = getPropertyValue(HtmlConstants.CSS_COLOR);
        if (StringUtils.isNotBlank(color)) {
            String hex = Colors.fromStyle(color);
            RenderUtils.getColor(rPr).setVal(hex);
        }

        String caps = getPropertyValue(HtmlConstants.CSS_FONT_VARIANT_CAPS);
        if (HtmlConstants.SMALL_CAPS.equals(caps)) {
            rPr.addNewSmallCaps();
        }

        // 中划线/下划线
        String textDecoration = getPropertyValue(HtmlConstants.CSS_TEXT_DECORATION_LINE);
        if (HtmlConstants.NONE.equals(textDecoration)) {
            RenderUtils.getUnderline(rPr).setVal(STUnderline.NONE);
        } else {
            if (StringUtils.contains(textDecoration, HtmlConstants.LINE_THROUGH)) {
                rPr.addNewStrike();
            }
            if (StringUtils.contains(textDecoration, HtmlConstants.UNDERLINE)) {
                CTUnderline ctUnderline = RenderUtils.getUnderline(rPr);
                String textDecorationStyle = getPropertyValue(HtmlConstants.CSS_TEXT_DECORATION_STYLE);
                ctUnderline.setVal(RenderUtils.underline(textDecorationStyle));
                String textDecorationColor = getPropertyValue(HtmlConstants.CSS_TEXT_DECORATION_COLOR);
                if (StringUtils.isNotBlank(textDecorationColor)) {
                    String hex = Colors.fromStyle(textDecorationColor);
                    ctUnderline.setColor(hex);
                }
            }
        }

        // 上下标
        String verticalAlign = getPropertyValue(HtmlConstants.CSS_VERTICAL_ALIGN);
        if (HtmlConstants.SUPER.equals(verticalAlign)) {
            rPr.addNewVertAlign().setVal(STVerticalAlignRun.SUPERSCRIPT);
        } else if (HtmlConstants.SUB.equals(verticalAlign)) {
            rPr.addNewVertAlign().setVal(STVerticalAlignRun.SUBSCRIPT);
        }

        // FIXME 段落边框与行内边框分离,行内只有全边框,段落分四边

        // 背景色
        String backgroundColor = getPropertyValue(HtmlConstants.CSS_BACKGROUND_COLOR, true);
        if (StringUtils.isNotBlank(backgroundColor)) {
            String hex = Colors.fromStyle(backgroundColor, null);
            if (hex != null) {
                CTShd ctShd = rPr.addNewShd();
                ctShd.setFill(hex);
                ctShd.setVal(STShd.CLEAR);
            }
        }

        // 可见性
        String visibility = getPropertyValue(HtmlConstants.CSS_VISIBILITY);
        if (HtmlConstants.HIDDEN.equals(visibility) || HtmlConstants.COLLAPSE.equals(visibility)) {
            rPr.addNewVanish();
        }
    }

    /**
     * 渲染图片
     *
     * @param pictureData 图片数据流
     * @param pictureType 图片类型
     * @param filename 文件名
     * @param width 宽度
     * @param height 高度
     * @param svgData SVG数据
     */
    public void renderPicture(InputStream pictureData, int pictureType, String filename, int width, int height, byte[] svgData)
            throws IOException, InvalidFormatException {
        CTR ctr = newRun();

        XWPFPicture xwpfPicture = currentRun.addPicture(pictureData, pictureType, filename, width, height);
        CTR r = currentRun.getCTR();

        boolean isSvg = svgData != null;
        if (isSvg) {
            attachSvgData(xwpfPicture, svgData);
        }

        CSSStyleDeclarationImpl styleDeclaration = currentElementStyle();
        String cssFloat = styleDeclaration.getPropertyValue(HtmlConstants.CSS_FLOAT);
        boolean floatLeft = HtmlConstants.LEFT.equals(cssFloat);
        boolean floatRight = !floatLeft && HtmlConstants.RIGHT.equals(cssFloat);
        boolean floatCenter = !floatLeft && !floatRight
                && HtmlConstants.AUTO.equals(styleDeclaration.getPropertyValue(HtmlConstants.CSS_MARGIN_LEFT))
                && HtmlConstants.AUTO.equals(styleDeclaration.getPropertyValue(HtmlConstants.CSS_MARGIN_RIGHT));
        // vertical-align seems not working
        boolean floated = floatLeft || floatRight || floatCenter;

        CTDrawing drawing = null;
        if (r != ctr) {
            int lastDrawingIndex = r.sizeOfDrawingArray() - 1;
            drawing = r.getDrawingArray(lastDrawingIndex);
            ctr.setDrawingArray(new CTDrawing[]{drawing});
            r.removeDrawing(lastDrawingIndex);
            drawing = ctr.getDrawingArray(0);
        } else if (isSvg || floated) {
            drawing = ctr.getDrawingArray(ctr.sizeOfDrawingArray() - 1);
        }
        previousDrawingRun = ctr;

        if (drawing != null && drawing.sizeOfInlineArray() > 0) {
            if (floated) {
                previousDrawingRun = null;
                CTAnchor ctAnchor = RenderUtils.inlineToAnchor(drawing);
                CTPosH ctPosH = ctAnchor.addNewPositionH();
                ctPosH.setRelativeFrom(STRelFromH.MARGIN);

                if (floatCenter) {
                    moveContentToNewPrevParagraph(ctr);
                    ctAnchor.addNewWrapTopAndBottom();
                    ctPosH.setAlign(STAlignH.CENTER);
                } else {
                    ctAnchor.addNewWrapSquare().setWrapText(STWrapText.LARGEST);
                    ctPosH.setAlign(floatRight ? STAlignH.RIGHT : STAlignH.LEFT);
                }

                CTPosV ctPosV = ctAnchor.addNewPositionV();
                ctPosV.setRelativeFrom(STRelFromV.PARAGRAPH);
                ctPosV.setAlign(STAlignV.TOP);

                if (isSvg) {
                    CTNonVisualGraphicFrameProperties properties = ctAnchor.addNewCNvGraphicFramePr();
                    CTGraphicalObjectFrameLocking frameLocking = properties.addNewGraphicFrameLocks();
                    frameLocking.setNoChangeAspect(true);
                }
            } else if (isSvg) {
                CTInline ctInline = drawing.getInlineArray(0);
                CTNonVisualGraphicFrameProperties properties = ctInline.isSetCNvGraphicFramePr()
                        ? ctInline.getCNvGraphicFramePr() : ctInline.addNewCNvGraphicFramePr();
                CTGraphicalObjectFrameLocking frameLocking = properties.isSetGraphicFrameLocks()
                        ? properties.getGraphicFrameLocks() : properties.addNewGraphicFrameLocks();
                frameLocking.setNoChangeAspect(true);
            }
        }
    }

    /**
     * 附加SVG数据
     *
     * @param xwpfPicture 图片
     * @param svgData SVG数据
     * @throws InvalidFormatException 非法格式
     */
    private void attachSvgData(XWPFPicture xwpfPicture, byte[] svgData) throws InvalidFormatException {
        CTPicture ctPicture = xwpfPicture.getCTPicture();
        String svgRelId = getXWPFDocument().addPictureData(svgData, SVGPictureData.PICTURE_TYPE_SVG);
        CTBlip blip = ctPicture.getBlipFill().getBlip();
        if (blip != null) {
            CTOfficeArtExtensionList extList = blip.isSetExtLst() ? blip.getExtLst() : blip.addNewExtLst();
            CTOfficeArtExtension svgBitmap = extList.addNewExt();
            svgBitmap.setUri(SVGRelation.SVG_URI);
            XmlCursor cur = svgBitmap.newCursor();
            cur.toEndToken();
            cur.beginElement(SVGRelation.SVG_QNAME);
            cur.insertNamespace(SVGRelation.SVG_PREFIX, SVGRelation.MS_SVG_NS);
            cur.insertAttributeWithValue(SVGRelation.EMBED_TAG, svgRelId);
            cur.dispose();
        }
    }

    /**
     * 将长度换算为EMU
     *
     * @param length 长度
     * @return EMU
     */
    public int lengthToEMU(CSSLength length) {
        if (!length.isValid()) {
            throw new UnsupportedOperationException("Invalid CSS length");
        }
        if (!length.getUnit().isRelative()) {
            return length.toEMU();
        }
        double emu;
        switch (length.getUnit()) {
            case REM:
                emu = length.unitValue() * getDefaultFontSize().toEMU();
                break;
            case EM:
                emu = length.unitValue() * getInheritedFontSizeInHalfPoints() * Units.EMU_PER_POINT / 2;
                break;
            case VW:
                emu = length.unitValue() * getPageWidth().toEMU();
                break;
            case VH:
                emu = length.unitValue() * getPageHeight().toEMU();
                break;
            case VMIN:
                emu = length.unitValue() * Math.min(getPageWidth().toEMU(), getPageHeight().toEMU());
                break;
            case VMAX:
                emu = length.unitValue() * Math.max(getPageWidth().toEMU(), getPageHeight().toEMU());
                break;
            // Unable to determine the use of width or height as a relative length for percent unit
            default:
                throw new UnsupportedOperationException("Can not convert to EMU with length: " + length);
        }
        return (int) Math.rint(emu);
    }

    public NumberingContext getNumberingContext() {
        return numberingContext;
    }

    public CSSLength getPageWidth() {
        return pageWidth;
    }

    public CSSLength getPageHeight() {
        return pageHeight;
    }

    public CSSLength getMarginTop() {
        return marginTop;
    }

    public CSSLength getMarginRight() {
        return marginRight;
    }

    public CSSLength getMarginBottom() {
        return marginBottom;
    }

    public CSSLength getMarginLeft() {
        return marginLeft;
    }

    public int getAvailablePageWidth() {
        return availablePageWidth;
    }

    public int getAvailablePageHeight() {
        return availablePageHeight;
    }

    public MathRenderConfig getMathRenderConfig() {
        return mathRenderConfig;
    }

    public void setMathRenderConfig(MathRenderConfig mathRenderConfig) {
        this.mathRenderConfig = mathRenderConfig;
    }

    public XWPFRun getCurrentRun() {
        return currentRun;
    }

    public String getGlobalFont() {
        return globalFont;
    }

    public BigInteger getGlobalFontSize() {
        return globalFontSize;
    }

    public boolean isShowDefaultTableBorderInTableCell() {
        return showDefaultTableBorderInTableCell;
    }

    public void setShowDefaultTableBorderInTableCell(boolean showDefaultTableBorderInTableCell) {
        this.showDefaultTableBorderInTableCell = showDefaultTableBorderInTableCell;
    }

    public void setGlobalFont(String globalFont) {
        this.globalFont = globalFont;
    }

    public void setGlobalFontSize(BigInteger globalFontSize) {
        this.globalFontSize = globalFontSize;
    }

    public boolean isBlocked() {
        return blockLevel > 0;
    }

    public void incrementBlockLevel() {
        blockLevel++;
    }

    public void decrementBlockLevel() {
        blockLevel--;
    }

    public void renderDocument(Document document) {
        Element body = document.body();
        Element html = body.parent();
        if (html.hasAttr(HtmlConstants.ATTR_STYLE)) {
            pushInlineStyle(getCssStyleDeclaration(html), html.isBlock());
        }
        if (body.hasAttr(HtmlConstants.ATTR_STYLE)) {
            pushInlineStyle(getCssStyleDeclaration(body), body.isBlock());
        }
        for (Node node : body.childNodes()) {
            renderNode(node);
        }
        globalCursor.dispose();
    }

    public void renderNode(Node node) {
        boolean isElement = node instanceof Element;

        if (isElement) {
            Element element = ((Element) node);
            renderElement(element);
        } else if (node instanceof TextNode) {
            renderText(((TextNode) node).getWholeText());
        }
    }

    public void renderElement(Element element) {
        if (log.isDebugEnabled()) {
            log.info("Start rendering html tag: <{}{}>", element.normalName(), element.attributes());
        }
        if (element.tag().isFormListed() || element.tag().isFormSubmittable()) {
            return;
        }

        CSSStyleDeclarationImpl cssStyleDeclaration = getCssStyleDeclaration(element);
        String display = cssStyleDeclaration.getPropertyValue(HtmlConstants.CSS_DISPLAY);
        if (HtmlConstants.NONE.equalsIgnoreCase(display)) {
            return;
        }
        pushInlineStyle(cssStyleDeclaration, element.isBlock());

        ElementRenderer elementRenderer = rendererProvider.get(element.normalName());
        boolean blocked = false;

        if (renderAsBlock(element, elementRenderer)) {
            if (element.childNodeSize() == 0 && !HtmlConstants.KEEP_EMPTY_TAGS.contains(element.normalName())) {
                popInlineStyle();
                return;
            }
            if (!isBlocked()) {
                // 复制段落中占位符之前的部分内容
                moveContentToNewPrevParagraph(getRun().getCTR());
            }
            incrementBlockLevel();
            blocked = true;

            IBody container = getContainer();
            boolean isTableTag = HtmlConstants.TAG_TABLE.equals(element.normalName());

            adjustCursor(container, isTableTag);

            if (isTableTag) {
                globalCursor.push();
                XWPFTable xwpfTable = container.insertNewTbl(globalCursor);
                globalCursor.pop();
                if (dedupeParagraph != null && !numberingContext.contains(dedupeParagraph)) {
                    if (!dedupeParagraph.equals(getRun().getParent()) && isEmptyParagraph(dedupeParagraph)) {
                        removeParagraph(container, dedupeParagraph);
                    }
                    unmarkDedupe();
                }
                // 新增时会自动创建一行一列,会影响自定义的表格渲染逻辑,故删除
                xwpfTable.removeRow(0);

                if (container.getPartType() == BodyType.TABLECELL && isShowDefaultTableBorderInTableCell()) {
                    CTTbl ctTbl = xwpfTable.getCTTbl();
                    CTTblBorders tblBorders = RenderUtils.getTblBorders(ctTbl);
                    tblBorders.addNewTop().setVal(STBorder.SINGLE);
                    tblBorders.addNewLeft().setVal(STBorder.SINGLE);
                    tblBorders.addNewBottom().setVal(STBorder.SINGLE);
                    tblBorders.addNewRight().setVal(STBorder.SINGLE);
                    tblBorders.addNewInsideH().setVal(STBorder.SINGLE);
                    tblBorders.addNewInsideV().setVal(STBorder.SINGLE);
                }

                RenderUtils.tableStyle(this, xwpfTable, cssStyleDeclaration);
            } else if (shouldNewParagraph(element)) {
                globalCursor.push();
                XWPFParagraph xwpfParagraph = newParagraph(container, globalCursor);
                globalCursor.pop();
                if (xwpfParagraph == null) {
                    log.warn("Can not add new paragraph for element: {}, attributes: {}", element.tagName(), element.attributes().html());
                }

                RenderUtils.paragraphStyle(this, xwpfParagraph, cssStyleDeclaration);
            } else {
                RenderUtils.paragraphStyle(this, dedupeParagraph, cssStyleDeclaration);
            }
        }

        if (elementRenderer != null) {
            if (!elementRenderer.renderStart(element, this)) {
                renderElementEnd(element, this, elementRenderer, blocked);
                return;
            }
        }

        for (Node child : element.childNodes()) {
            renderNode(child);
        }

        renderElementEnd(element, this, elementRenderer, blocked);
    }

    private boolean isEmptyParagraph(XWPFParagraph paragraph) {
        for (XWPFRun run : paragraph.getRuns()) {
            if (StringUtils.isNotBlank(run.text())) {
                return false;
            }
            if (!run.getEmbeddedPictures().isEmpty()) {
                return false;
            }
        }
        CTP ctp = paragraph.getCTP();
        return ctp.sizeOfOMathArray() == 0 && ctp.sizeOfOMathParaArray() == 0;
    }

    private void removeParagraph(IBody container, XWPFParagraph paragraph) {
        switch (container.getPartType()) {
            case CONTENTCONTROL:
                break;
            case DOCUMENT:
                XWPFDocument xwpfDocument = (XWPFDocument) container;
                int posOfParagraph = xwpfDocument.getPosOfParagraph(paragraph);
                xwpfDocument.removeBodyElement(posOfParagraph);
                break;
            case HEADER:
                XWPFHeader xwpfHeader = (XWPFHeader) container;
                xwpfHeader.removeParagraph(paragraph);
                break;
            case FOOTER:
                XWPFFooter xwpfFooter = (XWPFFooter) container;
                xwpfFooter.removeParagraph(paragraph);
                break;
            case FOOTNOTE:
                XWPFFootnote xwpfFootnote = (XWPFFootnote) container;
                xwpfFootnote.getParagraphs().remove(paragraph);
                break;
            case TABLECELL:
                XWPFTableCell xwpfTableCell = (XWPFTableCell) container;
                xwpfTableCell.removeParagraph(xwpfTableCell.getParagraphs().indexOf(paragraph));
                break;
        }
    }

    /**
     * HTML元素是否按照块状进行渲染
     *
     * @param element HTML元素
     * @return 是否按照块状进行渲染
     */
    public boolean renderAsBlock(Element element) {
        return renderAsBlock(element, rendererProvider.get(element.normalName()));
    }

    /**
     * HTML元素是否按照块状进行渲染
     *
     * @param element HTML元素
     * @param elementRenderer 元素渲染器
     * @return 是否按照块状进行渲染
     */
    private boolean renderAsBlock(Element element, ElementRenderer elementRenderer) {
        return element.isBlock() && (elementRenderer == null || elementRenderer.renderAsBlock());
    }

    private boolean shouldNewParagraph(Element element) {
        if (dedupeParagraph == null) {
            return true;
            // return !HtmlConstants.TAG_HR.equals(element.normalName());
        }
        boolean newParagraph = false;
        XmlCursor xmlCursor = dedupeParagraph.getCTP().newCursor();
        xmlCursor.push();
        if (xmlCursor.toPrevSibling()) {
            if (XmlUtils.P_QNAME.equals(xmlCursor.getName())) {
                newParagraph = removeLastBrRun(xmlCursor);
            }
        }

        if (!newParagraph) {
            xmlCursor.pop();
            newParagraph = removeLastBrRun(xmlCursor);
        }

        xmlCursor.dispose();
        return newParagraph;
    }

    private boolean removeLastBrRun(XmlCursor xmlCursor) {
        boolean removed = false;
        if (xmlCursor.toLastChild()) {
            if (XmlUtils.R_QNAME.equals(xmlCursor.getName())) {
                xmlCursor.push();
                if (xmlCursor.toFirstChild() && XmlUtils.BR_QNAME.equals(xmlCursor.getName()) && !xmlCursor.toNextSibling()) {
                    xmlCursor.pop();
                    xmlCursor.removeXml();
                    unmarkDedupe();
                    removed = true;
                } else {
                    xmlCursor.pop();
                }
            }
        }
        return removed;
    }

    private void adjustCursor(IBody container, boolean isTableTag) {
        if (XmlUtils.R_QNAME.equals(globalCursor.getName())) {
            globalCursor.push();
            globalCursor.toParent();
        }
        globalCursor.push();
        // 如果是表格,检查当前word容器的前一个兄弟元素是否为表格,是则插入一个段落,防止表格粘连在一起
        if (isTableTag && globalCursor.toPrevSibling()) {
            if (XmlUtils.TBL_QNAME.equals(globalCursor.getName())) {
                // pop() is safer than toNextSibling()
                globalCursor.pop();
                globalCursor.push();
                XWPFParagraph paragraph = newParagraph(container, globalCursor);
                unmarkDedupe();
                RenderUtils.paragraphStyle(this, paragraph, CSSStyleUtils.EMPTY_STYLE);
            }
        }
        globalCursor.pop();
    }

    private void renderElementEnd(Element element, HtmlRenderContext context, ElementRenderer elementRenderer, boolean blocked) {
        if (elementRenderer != null) {
            elementRenderer.renderEnd(element, context);
        }
        context.popInlineStyle();
        if (blocked) {
            context.decrementBlockLevel();
        }
    }

    private void moveContentToNewPrevParagraph(CTR ctr) {
        XmlCursor rCursor = ctr.newCursor();
        boolean hasPrevSibling = false;
        while (rCursor.toPrevSibling()) {
            XmlObject object = rCursor.getObject();
            if (object instanceof CTMarkupRange) {
                continue;
            }
            if (!XmlUtils.PPR_QNAME.equals(rCursor.getName())) {
                hasPrevSibling = true;
                break;
            }
        }
        if (!hasPrevSibling) {
            rCursor.dispose();
            return;
        }
        rCursor.toParent();
        rCursor.push();
        CTP ctp = ((CTP) rCursor.getObject());
        XWPFParagraph paragraph = getContainer().getParagraph(ctp);
        XWPFParagraph newParagraph = getContainer().insertNewParagraph(rCursor);
        XmlCursor pCursor = newParagraph.getCTP().newCursor();
        pCursor.toEndToken();
        rCursor.pop();
        rCursor.toFirstChild();

        XmlObject previousRun = null;
        if (previousText != null) {
            XmlCursor tCursor = previousText.getText().newCursor();
            tCursor.toParent();
            previousRun = tCursor.getObject();
            tCursor.dispose();
        }

        while (true) {
            XmlObject object = rCursor.getObject();
            if (ctr == object) break;
            QName name = rCursor.getName();
            if (XmlUtils.PPR_QNAME.equals(name)) {
                rCursor.copyXml(pCursor);
                rCursor.toNextSibling();
            } else if (XmlUtils.BOOKMARK_START_QNAME.equals(name) || XmlUtils.BOOKMARK_END_QNAME.equals(name)) {
                rCursor.toNextSibling();
            } else {
                if (previousDrawingRun == object) {
                    previousDrawingRun = null;
                }
                if (previousRun == object) {
                    previousText = null;
                }
                // moveXml附带了toNextSibling的效果
                rCursor.moveXml(pCursor);
            }
        }
        rCursor.dispose();
        pCursor.dispose();

        XWPFParagraphRuns runs = new XWPFParagraphRuns(paragraph);

        for (int i = runs.runCount() - ctp.getRList().size() - 1; i >= 0; i--) {
            runs.remove(i);
        }
    }

    public CSSStyleDeclarationImpl getCssStyleDeclaration(Element element) {
        String style = element.attr(HtmlConstants.ATTR_STYLE);
        CSSStyleDeclarationImpl cssStyleDeclaration = CSSStyleUtils.parse(style);
        CSSStyleUtils.split(cssStyleDeclaration);
        return cssStyleDeclaration;
    }

    /**
     * 保存当前指针位置并移动到目标指针位置
     *
     * @param targetCursor 目标指针
     */
    public void pushCursor(XmlCursor targetCursor) {
        globalCursor.push();
        globalCursor.toCursor(targetCursor);
    }

    /**
     * 返回之前保存的指针位置
     *
     * @return 是否返回成功
     */
    public boolean popCursor() {
        return globalCursor.pop();
    }

    /**
     * @return 指针当前指向的对象
     */
    public XmlObject currentCursorObject() {
        return globalCursor.getObject();
    }

    /**
     * 标记段落以防止块状元素嵌套产生多余的空段落
     *
     * @param paragraph 段落
     */
    public void markDedupe(XWPFParagraph paragraph) {
        dedupeParagraph = paragraph;
    }

    /**
     * 取消段落防重标记
     */
    public void unmarkDedupe() {
        dedupeParagraph = null;
    }

    /**
     * 文本封装类,用于空白字符折叠处理
     */
    private static class TextWrapper {
        private final CTText text;
        private final boolean endTrimmed;

        public TextWrapper(CTText text, boolean endTrimmed) {
            this.text = text;
            this.endTrimmed = endTrimmed;
        }

        public CTText getText() {
            return text;
        }

        public boolean isEndTrimmed() {
            return endTrimmed;
        }
    }
}


================================================
FILE: src/main/java/org/ddr/poi/html/HtmlRenderPolicy.java
================================================
/*
 * Copyright 2016 - 2021 Draco, https://github.com/draco1023
 *
 * 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
 *
 *     http://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.
 */

package org.ddr.poi.html;

import com.deepoove.poi.policy.AbstractRenderPolicy;
import com.deepoove.poi.render.RenderContext;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.xwpf.usermodel.BodyElementType;
import org.apache.poi.xwpf.usermodel.BodyType;
import org.apache.poi.xwpf.usermodel.IBody;
import org.apache.poi.xwpf.usermodel.IBodyElement;
import org.apache.poi.xwpf.usermodel.XWPFRun;
import org.apache.poi.xwpf.usermodel.XWPFTableCell;
import org.apache.xmlbeans.XmlCursor;
import org.ddr.poi.html.tag.ARenderer;
import org.ddr.poi.html.tag.BigRenderer;
import org.ddr.poi.html.tag.BoldRenderer;
import org.ddr.poi.html.tag.BreakRenderer;
import org.ddr.poi.html.tag.DeleteRenderer;
import org.ddr.poi.html.tag.FigureCaptionRenderer;
import org.ddr.poi.html.tag.FigureRenderer;
import org.ddr.poi.html.tag.HeaderBreakRenderer;
import org.ddr.poi.html.tag.HeaderRenderer;
import org.ddr.poi.html.tag.ImageRenderer;
import org.ddr.poi.html.tag.ItalicRenderer;
import org.ddr.poi.html.tag.LaTeXRenderer;
import org.ddr.poi.html.tag.ListItemRenderer;
import org.ddr.poi.html.tag.ListRenderer;
import org.ddr.poi.html.tag.MarkRenderer;
import org.ddr.poi.html.tag.MathRenderer;
import org.ddr.poi.html.tag.OmittedRenderer;
import org.ddr.poi.html.tag.PreRenderer;
import org.ddr.poi.html.tag.RubyRenderer;
import org.ddr.poi.html.tag.SmallRenderer;
import org.ddr.poi.html.tag.SubscriptRenderer;
import org.ddr.poi.html.tag.SuperscriptRenderer;
import org.ddr.poi.html.tag.SvgRenderer;
import org.ddr.poi.html.tag.TableCellRenderer;
import org.ddr.poi.html.tag.TableRenderer;
import org.ddr.poi.html.tag.UnderlineRenderer;
import org.ddr.poi.html.tag.WalkThroughRenderer;
import org.ddr.poi.html.util.CSSLength;
import org.ddr.poi.html.util.JsoupUtils;
import org.ddr.poi.util.XmlUtils;
import org.jsoup.nodes.Document;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTR;

import java.math.BigInteger;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * HTML字符串渲染策略
 *
 * @author Draco
 * @since 2021-02-07
 */
public class HtmlRenderPolicy extends AbstractRenderPolicy<String> {
    private final Map<String, ElementRenderer> elRenderers;
    private final HtmlRenderConfig config;

    public HtmlRenderPolicy() {
        this(new HtmlRenderConfig());
    }

    @Deprecated
    public HtmlRenderPolicy(String globalFont, CSSLength globalFontSize) {
        this(new HtmlRenderConfig());
        config.setGlobalFont(globalFont);
        config.setGlobalFontSize(globalFontSize);
    }

    public HtmlRenderPolicy(HtmlRenderConfig config) {
        ElementRenderer[] renderers = {
                new ARenderer(),
                new BigRenderer(),
                new BoldRenderer(),
                new BreakRenderer(),
                new DeleteRenderer(),
                new FigureRenderer(),
                new FigureCaptionRenderer(),
                new HeaderBreakRenderer(),
                new HeaderRenderer(),
                new ImageRenderer(),
                new ItalicRenderer(),
                new LaTeXRenderer(),
                new ListItemRenderer(),
                new ListRenderer(),
                new MarkRenderer(),
                new MathRenderer(),
                new OmittedRenderer(),
                new PreRenderer(),
                new RubyRenderer(),
                new SmallRenderer(),
                new SubscriptRenderer(),
                new SuperscriptRenderer(),
                new SvgRenderer(),
                new TableCellRenderer(),
                new TableRenderer(),
                new UnderlineRenderer(),
                new WalkThroughRenderer()
        };
        elRenderers = new HashMap<>(renderers.length);
        for (ElementRenderer renderer : renderers) {
            for (String tag : renderer.supportedTags()) {
                elRenderers.put(tag, renderer);
            }
        }
        this.config = config;
        // custom tag renderer will overwrite the built-in renderer
        if (config.getCustomRenderers() != null) {
            for (ElementRenderer customRenderer : config.getCustomRenderers()) {
                for (String tag : customRenderer.supportedTags()) {
                    elRenderers.put(tag, customRenderer);
                }
            }
        }
    }

    public HtmlRenderConfig getConfig() {
        return config;
    }

    @Override
    protected boolean validate(String data) {
        return StringUtils.isNotEmpty(data);
    }

    @Override
    public void doRender(RenderContext<String> context) throws Exception {
        Document document = JsoupUtils.parse(context.getData());
        document.outputSettings().prettyPrint(false).indentAmount(0);

        HtmlRenderContext htmlRenderContext = new HtmlRenderContext(context, elRenderers::get);
        htmlRenderContext.setGlobalFont(config.getGlobalFont());
        if (config.getGlobalFontSizeInHalfPoints() > 0) {
            htmlRenderContext.setGlobalFontSize(BigInteger.valueOf(config.getGlobalFontSizeInHalfPoints()));
        }
        htmlRenderContext.getNumberingContext().setIndent(config.getNumberingIndent());
        htmlRenderContext.getNumberingContext().setHanging(config.getNumberingHanging());
        htmlRenderContext.getNumberingContext().setSpacing(config.getNumberingSpacing());
        htmlRenderContext.setShowDefaultTableBorderInTableCell(config.isShowDefaultTableBorderInTableCell());
        htmlRenderContext.setMathRenderConfig(config.getMathRenderConfig());

        htmlRenderContext.renderDocument(document);
    }

    @Override
    protected void afterRender(RenderContext<String> context) {
        boolean hasSibling = hasSibling(context.getRun());
        clearPlaceholder(context, !hasSibling);

        IBody container = context.getContainer();
        if (container.getPartType() == BodyType.TABLECELL) {
            // 单元格的最后一个元素应为p,否则可能无法正常打开文件
            List<IBodyElement> bodyElements = container.getBodyElements();
            if (bodyElements.isEmpty() || bodyElements.get(bodyElements.size() - 1).getElementType() != BodyElementType.PARAGRAPH) {
                ((XWPFTableCell) container).addParagraph();
            }
        }
    }

    private boolean hasSibling(XWPFRun run) {
        boolean hasSibling = false;
        CTR ctr = run.getCTR();
        XmlCursor xmlCursor = ctr.newCursor();
        xmlCursor.push();
        while (xmlCursor.toNextSibling()) {
            if (isValidSibling(xmlCursor)) {
                hasSibling = true;
                break;
            }
        }
        if (!hasSibling) {
            xmlCursor.pop();
            while (xmlCursor.toPrevSibling()) {
                if (isValidSibling(xmlCursor)) {
                    hasSibling = true;
                    break;
                }
            }
        }
        xmlCursor.dispose();
        return hasSibling;
    }

    private boolean isValidSibling(XmlCursor cursor) {
        return !XmlUtils.INVALID_R_SIBLINGS.contains(cursor.getName());
    }

}


================================================
FILE: src/main/java/org/ddr/poi/html/tag/ARenderer.java
================================================
/*
 * Copyright 2016 - 2021 Draco, https://github.com/draco1023
 *
 * 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
 *
 *     http://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.
 */

package org.ddr.poi.html.tag;

import org.ddr.poi.html.ElementRenderer;
import org.ddr.poi.html.HtmlConstants;
import org.ddr.poi.html.HtmlRenderContext;
import org.jsoup.nodes.Element;

/**
 * a标签渲染器
 *
 * @author Draco
 * @since 2021-03-31
 */
public class ARenderer implements ElementRenderer {
    private static final String[] TAGS = {HtmlConstants.TAG_A};

    /**
     * 开始渲染
     *
     * @param element HTML元素
     * @param context 渲染上下文
     * @return 是否继续渲染子元素
     */
    @Override
    public boolean renderStart(Element element, HtmlRenderContext context) {
        String href = element.attr(HtmlConstants.ATTR_HREF);
        context.startHyperlink(href);
        return true;
    }

    /**
     * 元素渲染结束需要执行的逻辑
     *
     * @param element HTML元素
     * @param context 渲染上下文
     */
    @Override
    public void renderEnd(Element element, HtmlRenderContext context) {
        context.endHyperlink();
    }

    @Override
    public String[] supportedTags() {
        return TAGS;
    }

    @Override
    public boolean renderAsBlock() {
        return false;
    }
}


================================================
FILE: src/main/java/org/ddr/poi/html/tag/BigRenderer.java
================================================
/*
 * Copyright 2016 - 2021 Draco, https://github.com/draco1023
 *
 * 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
 *
 *     http://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.
 */

package org.ddr.poi.html.tag;

import org.ddr.poi.html.ElementRenderer;
import org.ddr.poi.html.HtmlConstants;
import org.ddr.poi.html.HtmlRenderContext;
import org.ddr.poi.html.util.CSSStyleUtils;
import org.jsoup.nodes.Element;

/**
 * big标签渲染器,HTML5不支持big
 *
 * @author Draco
 * @since 2021-02-23
 */
@Deprecated
public class BigRenderer implements ElementRenderer {
    private static final String[] TAGS = {HtmlConstants.TAG_BIG};

    /**
     * 开始渲染
     *
     * @param element HTML元素
     * @param context 渲染上下文
     * @return 是否继续渲染子元素
     */
    @Override
    public boolean renderStart(Element element, HtmlRenderContext context) {
        context.pushInlineStyle(CSSStyleUtils.parse(HtmlConstants.DEFINED_LARGER), element.isBlock());
        return true;
    }

    /**
     * 元素渲染结束需要执行的逻辑
     *
     * @param element HTML元素
     * @param context 渲染上下文
     */
    @Override
    public void renderEnd(Element element, HtmlRenderContext context) {
        context.popInlineStyle();
    }

    @Override
    public String[] supportedTags() {
        return TAGS;
    }

    @Override
    public boolean renderAsBlock() {
        return false;
    }
}


================================================
FILE: src/main/java/org/ddr/poi/html/tag/BoldRenderer.java
================================================
/*
 * Copyright 2016 - 2021 Draco, https://github.com/draco1023
 *
 * 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
 *
 *     http://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.
 */

package org.ddr.poi.html.tag;

import org.ddr.poi.html.ElementRenderer;
import org.ddr.poi.html.HtmlConstants;
import org.ddr.poi.html.HtmlRenderContext;
import org.ddr.poi.html.util.CSSStyleUtils;
import org.jsoup.nodes.Element;

/**
 * 加粗标签渲染器
 *
 * @author Draco
 * @since 2021-02-23
 */
public class BoldRenderer implements ElementRenderer {
    private static final String[] TAGS = {HtmlConstants.TAG_B, HtmlConstants.TAG_STRONG};

    /**
     * 开始渲染
     *
     * @param element HTML元素
     * @param context 渲染上下文
     * @return 是否继续渲染子元素
     */
    @Override
    public boolean renderStart(Element element, HtmlRenderContext context) {
        context.pushInlineStyle(CSSStyleUtils.parse(HtmlConstants.DEFINED_BOLD), element.isBlock());
        return true;
    }

    /**
     * 元素渲染结束需要执行的逻辑
     *
     * @param element HTML元素
     * @param context 渲染上下文
     */
    @Override
    public void renderEnd(Element element, HtmlRenderContext context) {
        context.popInlineStyle();
    }

    @Override
    public String[] supportedTags() {
        return TAGS;
    }

    @Override
    public boolean renderAsBlock() {
        return false;
    }
}


================================================
FILE: src/main/java/org/ddr/poi/html/tag/BreakRenderer.java
================================================
/*
 * Copyright 2016 - 2021 Draco, https://github.com/draco1023
 *
 * 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
 *
 *     http://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.
 */

package org.ddr.poi.html.tag;

import org.apache.poi.xwpf.usermodel.IRunBody;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.ddr.poi.html.ElementRenderer;
import org.ddr.poi.html.HtmlConstants;
import org.ddr.poi.html.HtmlRenderContext;
import org.jsoup.nodes.Element;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTR;

/**
 * br标签渲染器
 *
 * @author Draco
 * @since 2021-02-09
 */
public class BreakRenderer implements ElementRenderer {
    private static final String[] TAGS = {HtmlConstants.TAG_BR};

    /**
     * 开始渲染
     *
     * @param element HTML元素
     * @param context 渲染上下文
     * @return 是否继续渲染子元素
     */
    @Override
    public boolean renderStart(Element element, HtmlRenderContext context) {
        CTR ctr = context.newRun();
        ctr.addNewBr();
        IRunBody parent = context.getCurrentRun().getParent();
        if (parent instanceof XWPFParagraph) {
            context.markDedupe((XWPFParagraph) parent);
        }
        return false;
    }

    @Override
    public String[] supportedTags() {
        return TAGS;
    }

    @Override
    public boolean renderAsBlock() {
        return false;
    }
}


================================================
FILE: src/main/java/org/ddr/poi/html/tag/DeleteRenderer.java
================================================
/*
 * Copyright 2016 - 2021 Draco, https://github.com/draco1023
 *
 * 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
 *
 *     http://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.
 */

package org.ddr.poi.html.tag;

import org.ddr.poi.html.ElementRenderer;
import org.ddr.poi.html.HtmlConstants;
import org.ddr.poi.html.HtmlRenderContext;
import org.ddr.poi.html.util.CSSStyleUtils;
import org.jsoup.nodes.Element;

/**
 * 删除线标签渲染器
 *
 * @author Draco
 * @since 2021-02-23
 */
public class DeleteRenderer implements ElementRenderer {
    private static final String[] TAGS = {HtmlConstants.TAG_S, HtmlConstants.TAG_DEL, HtmlConstants.TAG_STRIKE};

    /**
     * 开始渲染
     *
     * @param element HTML元素
     * @param context 渲染上下文
     * @return 是否继续渲染子元素
     */
    @Override
    public boolean renderStart(Element element, HtmlRenderContext context) {
        context.pushInlineStyle(CSSStyleUtils.parse(HtmlConstants.DEFINED_STRIKE), element.isBlock());
        return true;
    }

    /**
     * 元素渲染结束需要执行的逻辑
     *
     * @param element HTML元素
     * @param context 渲染上下文
     */
    @Override
    public void renderEnd(Element element, HtmlRenderContext context) {
        context.popInlineStyle();
    }

    @Override
    public String[] supportedTags() {
        return TAGS;
    }

    @Override
    public boolean renderAsBlock() {
        return false;
    }
}


================================================
FILE: src/main/java/org/ddr/poi/html/tag/FigureCaptionRenderer.java
================================================
package org.ddr.poi.html.tag;

import org.ddr.poi.html.ElementRenderer;
import org.ddr.poi.html.HtmlConstants;
import org.ddr.poi.html.HtmlRenderContext;
import org.jsoup.nodes.Element;

/**
 * figcaption标签渲染器
 *
 * @author Draco
 * @since 2022-11-03
 */
public class FigureCaptionRenderer implements ElementRenderer {
    private static final String[] TAGS = {HtmlConstants.TAG_FIGURE_CAPTION};

    /**
     * 开始渲染
     *
     * @param element HTML元素
     * @param context 渲染上下文
     * @return 是否继续渲染子元素
     */
    @Override
    public boolean renderStart(Element element, HtmlRenderContext context) {
        context.markDedupe(context.getClosestParagraph());
        return true;
    }

    /**
     * 元素渲染结束需要执行的逻辑
     *
     * @param element HTML元素
     * @param context 渲染上下文
     */
    @Override
    public void renderEnd(Element element, HtmlRenderContext context) {
        context.unmarkDedupe();
    }

    /**
     * @return 支持的HTML标签
     */
    @Override
    public String[] supportedTags() {
        return TAGS;
    }

    /**
     * @return 是否为块状渲染,如果为true在Word中会另起一个Paragraph
     */
    @Override
    public boolean renderAsBlock() {
        return true;
    }
}


================================================
FILE: src/main/java/org/ddr/poi/html/tag/FigureRenderer.java
================================================
package org.ddr.poi.html.tag;

import com.steadystate.css.dom.CSSStyleDeclarationImpl;
import org.apache.poi.xwpf.usermodel.ParagraphAlignment;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.ddr.poi.html.ElementRenderer;
import org.ddr.poi.html.HtmlConstants;
import org.ddr.poi.html.HtmlRenderContext;
import org.ddr.poi.html.util.JsoupUtils;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

/**
 * figure标签渲染器
 *
 * @author Draco
 * @since 2022-11-03
 */
public class FigureRenderer implements ElementRenderer {
    private static final String[] TAGS = {HtmlConstants.TAG_FIGURE};

    /**
     * 开始渲染
     *
     * @param element HTML元素
     * @param context 渲染上下文
     * @return 是否继续渲染子元素
     */
    @Override
    public boolean renderStart(Element element, HtmlRenderContext context) {
        // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/figure#usage_notes
        Elements captions = JsoupUtils.children(element, HtmlConstants.TAG_FIGURE_CAPTION);
        if (captions.size() > 1) {
            captions.remove(0);
            captions.remove();
        }

        XWPFParagraph paragraph = context.getClosestParagraph();
        context.markDedupe(paragraph);

        CSSStyleDeclarationImpl styleDeclaration = context.currentElementStyle();
        String cssFloat = styleDeclaration.getPropertyValue(HtmlConstants.CSS_FLOAT);
        if (HtmlConstants.LEFT.equals(cssFloat)) {
            paragraph.setAlignment(ParagraphAlignment.LEFT);
            styleDeclaration.setTextAlign(HtmlConstants.LEFT);
        } else if (HtmlConstants.RIGHT.equals(cssFloat)) {
            paragraph.setAlignment(ParagraphAlignment.RIGHT);
            styleDeclaration.setTextAlign(HtmlConstants.RIGHT);
        } else {
            paragraph.setAlignment(ParagraphAlignment.CENTER);
            styleDeclaration.setTextAlign(HtmlConstants.CENTER);
        }

        return true;
    }

    /**
     * 元素渲染结束需要执行的逻辑
     *
     * @param element HTML元素
     * @param context 渲染上下文
     */
    @Override
    public void renderEnd(Element element, HtmlRenderContext context) {
        context.unmarkDedupe();
    }

    /**
     * @return 支持的HTML标签
     */
    @Override
    public String[] supportedTags() {
        return TAGS;
    }

    /**
     * @return 是否为块状渲染,如果为true在Word中会另起一个Paragraph
     */
    @Override
    public boolean renderAsBlock() {
        return true;
    }
}


================================================
FILE: src/main/java/org/ddr/poi/html/tag/HeaderBreakRenderer.java
================================================
/*
 * Copyright 2016 - 2021 Draco, https://github.com/draco1023
 *
 * 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
 *
 *     http://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.
 */

package org.ddr.poi.html.tag;

import org.ddr.poi.html.ElementRenderer;
import org.ddr.poi.html.HtmlConstants;
import org.ddr.poi.html.HtmlRenderContext;
import org.ddr.poi.html.util.RenderUtils;
import org.jsoup.nodes.Element;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTBorder;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTP;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTPBdr;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STBorder;

import java.math.BigInteger;

/**
 * hr标签渲染器
 *
 * @author Draco
 * @since 2021-02-18
 */
public class HeaderBreakRenderer implements ElementRenderer {
    private static final String[] TAGS = {HtmlConstants.TAG_HR};
    /**
     * 线粗细,相当于3px
     */
    private static final BigInteger SIZE = BigInteger.valueOf(6);
    /**
     * 间距
     */
    private static final BigInteger SPACE = BigInteger.ONE;

    /**
     * 开始渲染
     *
     * @param element HTML元素
     * @param context 渲染上下文
     * @return 是否继续渲染子元素
     */
    @Override
    public boolean renderStart(Element element, HtmlRenderContext context) {
        CTP ctp = context.getClosestParagraph().getCTP();
        CTPBdr pBdr = RenderUtils.getPBdr(RenderUtils.getPPr(ctp));
        CTBorder ctBorder = pBdr.addNewBottom();
        ctBorder.setVal(STBorder.SINGLE);
        ctBorder.setSz(SIZE);
        ctBorder.setSpace(SPACE);

        ctBorder = pBdr.addNewBetween();
        ctBorder.setVal(STBorder.SINGLE);
        ctBorder.setSz(SIZE);
        ctBorder.setSpace(SPACE);
        return false;
    }

    @Override
    public void renderEnd(Element element, HtmlRenderContext context) {
        context.unmarkDedupe();
    }

    @Override
    public String[] supportedTags() {
        return TAGS;
    }

    @Override
    public boolean renderAsBlock() {
        return true;
    }
}


================================================
FILE: src/main/java/org/ddr/poi/html/tag/HeaderRenderer.java
================================================
/*
 * Copyright 2016 - 2021 Draco, https://github.com/draco1023
 *
 * 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
 *
 *     http://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.
 */

package org.ddr.poi.html.tag;

import org.ddr.poi.html.ElementRenderer;
import org.ddr.poi.html.HtmlConstants;
import org.ddr.poi.html.HtmlRenderContext;
import org.ddr.poi.html.util.CSSStyleUtils;
import org.ddr.poi.html.util.RenderUtils;
import org.jsoup.nodes.Element;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTDecimalNumber;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTP;

import java.math.BigInteger;

/**
 * h1~h6标签渲染器
 *
 * @author Draco
 * @since 2021-02-24
 */
public class HeaderRenderer implements ElementRenderer {
    private static final String[] TAGS = {
            HtmlConstants.TAG_H1, HtmlConstants.TAG_H2, HtmlConstants.TAG_H3,
            HtmlConstants.TAG_H4, HtmlConstants.TAG_H5, HtmlConstants.TAG_H6
    };

    /**
     * 各级别标题对应字号
     */
    private static final String[] FONT_SIZES = {"24pt", "18pt", "14pt", "12pt", "10pt", "7.5pt"};

    /**
     * 开始渲染
     *
     * @param element HTML元素
     * @param context 渲染上下文
     * @return 是否继续渲染子元素
     */
    @Override
    public boolean renderStart(Element element, HtmlRenderContext context) {
        int index = Integer.parseInt(element.normalName().substring(1)) - 1;
        String fontSizeStyle = HtmlConstants.inlineStyle(HtmlConstants.CSS_FONT_SIZE, FONT_SIZES[index]);
        context.pushInlineStyle(CSSStyleUtils.parse(HtmlConstants.DEFINED_BOLD + fontSizeStyle), element.isBlock());

        CTP ctp = context.getClosestParagraph().getCTP();
        CTDecimalNumber ctDecimalNumber = CTDecimalNumber.Factory.newInstance();
        ctDecimalNumber.setVal(BigInteger.valueOf(index));
        RenderUtils.getPPr(ctp).setOutlineLvl(ctDecimalNumber);
        return true;
    }

    /**
     * 元素渲染结束需要执行的逻辑
     *
     * @param element HTML元素
     * @param context 渲染上下文
     */
    @Override
    public void renderEnd(Element element, HtmlRenderContext context) {
        context.popInlineStyle();
    }

    @Override
    public String[] supportedTags() {
        return TAGS;
    }

    @Override
    public boolean renderAsBlock() {
        return true;
    }
}


================================================
FILE: src/main/java/org/ddr/poi/html/tag/ImageRenderer.java
================================================
/*
 * Copyright 2016 - 2021 Draco, https://github.com/draco1023
 *
 * 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
 *
 *     http://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.
 */

package org.ddr.poi.html.tag;

import com.drew.imaging.FileType;
import com.drew.imaging.FileTypeDetector;
import com.drew.imaging.ImageMetadataReader;
import com.drew.imaging.ImageProcessingException;
import com.drew.metadata.Metadata;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.util.Units;
import org.apache.poi.xwpf.usermodel.SVGPictureData;
import org.ddr.image.ImageInfo;
import org.ddr.image.ImageType;
import org.ddr.image.MetadataReader;
import org.ddr.image.MetadataReaders;
import org.ddr.poi.html.ElementRenderer;
import org.ddr.poi.html.HtmlConstants;
import org.ddr.poi.html.HtmlRenderContext;
import org.ddr.poi.html.util.CSSLength;
import org.ddr.poi.math.MathMLUtils;
import org.ddr.poi.util.ByteArrayCopyStream;
import org.ddr.poi.util.HttpURLConnectionUtils;
import org.jsoup.nodes.Element;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Iterator;

/**
 * img标签渲染器
 *
 * @author Draco
 * @since 2021-02-09
 */
public class ImageRenderer implements ElementRenderer {
    private static final Logger log = LoggerFactory.getLogger(ImageRenderer.class);

    private static final String[] TAGS = {HtmlConstants.TAG_IMG};
    private static final String HTTP = "http";
    private static final String DOUBLE_SLASH = "//";
    private static final String DATA_PREFIX = "data:";
    private static final String COMMENT_MATH_PREFIX = "<!--MathML: <math ";
    private static final String COMMENT_MATH_SUFFIX = "</math>-->";

    static {
        SVGPictureData.initRelation();
    }

    /**
     * 元素渲染结束需要执行的逻辑
     *
     * @param element HTML元素
     * @param context 渲染上下文
     */
    @Override
    public boolean renderStart(Element element, HtmlRenderContext context) {
        String src = element.attr(HtmlConstants.ATTR_SRC);
        if (StringUtils.startsWithIgnoreCase(src, HTTP)) {
            handleRemoteImage(element, context, src);
        } else if (StringUtils.startsWith(src, DOUBLE_SLASH)) {
            // 某些图片链接为了跟随网站协议而隐去了协议名称
            handleRemoteImage(element, context, HTTP + HtmlConstants.COLON + src);
        } else if (StringUtils.startsWith(src, DATA_PREFIX)) {
            handleData(element, context, src);
        }
        return false;
    }

    /**
     * 处理Data URL
     *
     * @param element HTML元素
     * @param context 渲染上下文
     * @param src 数据
     */
    private void handleData(Element element, HtmlRenderContext context, String src) {
        int index = src.indexOf(HtmlConstants.COMMA.charAt(0));
        String data = src.substring(index + 1);
        String declaration = src.substring(0, index);
        String format = StringUtils.substringBetween(declaration, HtmlConstants.SLASH, HtmlConstants.SEMICOLON);
        // org.apache.poi.sl.usermodel.PictureData.PictureType
        if (format.contains(HtmlConstants.MINUS)) {
            format = StringUtils.substringAfterLast(format, HtmlConstants.MINUS);
        } else if (format.contains(HtmlConstants.PLUS)) {
            format = StringUtils.substringBefore(format, HtmlConstants.PLUS);
        }

        byte[] bytes;
        if (declaration.contains("base64")) {
            try {
                bytes = Base64.getDecoder().decode(data);
            } catch (Exception e) {
                log.warn("Failed to load image due to illegal base64 data: {}", src);
                return;
            }
        } else {
            if (data.startsWith(HtmlConstants.PERCENT)) {
                try {
                    data = URLDecoder.decode(data, StandardCharsets.UTF_8.name());
                } catch (UnsupportedEncodingException e) {
                    log.warn("Failed to load image due to illegal data url: {}", src);
                    return;
                }
            }

            // wiris support
            int startOfMath = data.indexOf(COMMENT_MATH_PREFIX);
            if (startOfMath >= 0) {
                try {
                    int endOfMath = data.indexOf(COMMENT_MATH_SUFFIX, startOfMath + COMMENT_MATH_PREFIX.length());
                    String math = data.substring(startOfMath + 12, endOfMath + 7);
                    MathMLUtils.renderTo(context.getClosestParagraph(), context.newRun(), math, context.getMathRenderConfig());
                    return;
                } catch (Exception e) {
                    log.warn("Failed to render math in wiris svg, will try to render as svg image: {}", data, e);
                }
            }

            bytes = data.getBytes(StandardCharsets.UTF_8);
        }
        boolean svg = HtmlConstants.TAG_SVG.equals(format);
        try (ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes)) {
            ImageInfo info = analyzeImage(inputStream, svg);
            if (info == null) {
                log.warn("Illegal image url: {}", src);
                return;
            }
            addPicture(element, context, info.getStream(), info.getRawType(), info.getWidth(), info.getHeight(), svg ? bytes : null);
        } catch (IOException | InvalidFormatException e) {
            log.warn("Failed to load image: {}", src, e);
        }
    }

    /**
     * 根据图片反推类型
     *
     * @param image 图片
     * @return 图片类型
     */
    protected ImageType typeOf(BufferedImage image) {
        return image.getColorModel().hasAlpha() ? ImageType.PNG : ImageType.JPG;
    }

    /**
     * 处理远程图片
     *
     * @param element HTML元素
     * @param context 渲染上下文
     * @param src 图片链接地址
     */
    private void handleRemoteImage(Element element, HtmlRenderContext context, String src) {
        HttpURLConnection connect = null;
        try {
            connect = HttpURLConnectionUtils.connect(src);
            HttpURLConnectionUtils.initUserAgent(connect);
            int firstSlashPosition = src.indexOf('/', src.indexOf("://") + 3);
            connect.setRequestProperty("Referrer", src.substring(0, firstSlashPosition));

            InputStream urlStream = connect.getInputStream();
            boolean svg = StringUtils.contains(connect.getHeaderField("content-type"), HtmlConstants.TAG_SVG);
            ByteArrayCopyStream outputStream = new ByteArrayCopyStream(urlStream.available());
            IOUtils.copy(urlStream, outputStream);
            final byte[] svgData = svg ? outputStream.toByteArray() : null;

            ByteArrayInputStream inputStream = outputStream.toInput();
            ImageInfo info = analyzeImage(inputStream, svg);
            if (info == null) {
                log.warn("Illegal image url: {}", src);
                return;
            }

            addPicture(element, context, info.getStream(), info.getRawType(), info.getWidth(), info.getHeight(), svgData);
        } catch (IOException | InvalidFormatException e) {
            log.warn("Failed to load image: {}", src, e);
        } finally {
            IOUtils.close(connect);
        }
    }

    private ImageInfo analyzeImage(ByteArrayInputStream inputStream, boolean svg) throws IOException, InvalidFormatException {
        final long length = inputStream.available();
        // actual image data stream
        ByteArrayInputStream stream = inputStream;
        ImageType type = null;
        Dimension dimension = null;

        if (svg) {
            BufferedImage image = ImageIO.read(inputStream);
            inputStream.reset();

            type = typeOf(image);
            ByteArrayCopyStream imageStream = new ByteArrayCopyStream(image.getData().getDataBuffer().getSize());
            ImageIO.write(image, type.getExtension(), imageStream);
            stream = imageStream.toInput();

            dimension = new Dimension(image.getWidth(), image.getHeight());
        } else {
            FileType fileType = FileTypeDetector.detectFileType(inputStream);
            for (MetadataReader metadataReader : MetadataReaders.INSTANCES) {
                if (metadataReader.canRead(fileType)) {
                    try {
                        // FIXME metadata-extractor 一直未发版支持 AVIF 格式,会被归为 QuickTime 格式
                        if (fileType == FileType.QuickTime) {
                            fileType = FileType.Heif;
                        }
                        Metadata metadata = ImageMetadataReader.readMetadata(inputStream, length, fileType);
                        type = metadataReader.getType(metadata);
                        dimension = metadataReader.getDimension(metadata);
                        break;
                    } catch (ImageProcessingException ignored) {
                    }
                }
            }
            inputStream.reset();
            if (dimension == null) {
                Iterator<ImageReader> imageReaders = ImageIO.getImageReaders(inputStream);
                while (imageReaders.hasNext()) {
                    ImageReader reader = imageReaders.next();
                    try {
                        dimension = new Dimension(reader.getWidth(0), reader.getHeight(0));
                        break;
                    } catch (IOException ignored) {
                    }
                }
                if (dimension == null) {
                    BufferedImage image = ImageIO.read(inputStream);
                    inputStream.reset();

                    if (image == null) {
                        return null;
                    }

                    if (type == null) {
                        type = typeOf(image);
                    }
                    dimension = new Dimension(image.getWidth(), image.getHeight());
                }
            }
        }
        return new ImageInfo(stream, type, dimension);
    }

    @Override
    public String[] supportedTags() {
        return TAGS;
    }

    @Override
    public boolean renderAsBlock() {
        return false;
    }

    /**
     * 添加图片
     *
     * @param element HTML元素
     * @param context 渲染上下文
     * @param inputStream 图片数据流
     * @param type 图片类型
     * @param widthInPixels 图片宽度(像素)
     * @param heightInPixels 图片高度(像素)
     * @param svgData SVG数据
     */
    protected void addPicture(Element element, HtmlRenderContext context, InputStream inputStream, int type,
                              int widthInPixels, int heightInPixels,
                              byte[] svgData) throws InvalidFormatException, IOException {
        // 容器限制宽度
        int containerWidth = context.getAvailableWidthInEMU();
//        int containerHeight = context.getAvailablePageHeight();
        // 图片原始宽高
        int widthInEMU = Units.pixelToEMU(widthInPixels);
        int heightInEMU = Units.pixelToEMU(heightInPixels);
        float naturalAspect = 1f * widthInEMU / heightInEMU;

        int declaredWidth = widthInEMU;
        int declaredHeight = heightInEMU;
        int maxWidthInEMU = containerWidth;
        int maxHeightInEMU = Integer.MAX_VALUE;

        String width = context.getPropertyValue(HtmlConstants.CSS_WIDTH);
        if (width.length() > 0) {
            CSSLength cssLength = CSSLength.of(width);
            if (cssLength.isValid()) {
                declaredWidth = context.computeLengthInEMU(cssLength, widthInEMU, containerWidth);
            }
        } else {
            // width attribute is overridden by style, the same to height
            // https://css-tricks.com/whats-the-difference-between-width-height-in-css-and-width-height-html-attributes/
            width = element.attr(HtmlConstants.ATTR_WIDTH);
            if (NumberUtils.isParsable(width)) {
                width += HtmlConstants.PX;
                CSSLength cssLength = CSSLength.of(width);
                declaredWidth = context.computeLengthInEMU(cssLength, widthInEMU, containerWidth);
            }
        }


        String maxWidth = context.getPropertyValue(HtmlConstants.CSS_MAX_WIDTH);
        if (maxWidth.length() > 0) {
            CSSLength cssLength = CSSLength.of(maxWidth);
            if (cssLength.isValid()) {
                // restrained by container
                maxWidthInEMU = Math.min(context.computeLengthInEMU(cssLength, widthInEMU, containerWidth), containerWidth);
            }
        }

        String height = context.getPropertyValue(HtmlConstants.CSS_HEIGHT);
        if (height.length() > 0) {
            CSSLength cssLength = CSSLength.of(height);
            if (cssLength.isValid()) {
                declaredHeight = context.computeLengthInEMU(cssLength, heightInEMU, Integer.MAX_VALUE);
            }
        } else {
            height = element.attr(HtmlConstants.ATTR_HEIGHT);
            if (NumberUtils.isParsable(height)) {
                height += HtmlConstants.PX;
                CSSLength cssLength = CSSLength.of(height);
                declaredHeight = context.computeLengthInEMU(cssLength, heightInEMU, Integer.MAX_VALUE);
            }
        }

        String maxHeight = context.getPropertyValue(HtmlConstants.CSS_MAX_HEIGHT);
        if (maxHeight.length() > 0) {
            CSSLength cssLength = CSSLength.of(maxHeight);
            if (cssLength.isValid()) {
                maxHeightInEMU = context.computeLengthInEMU(cssLength, heightInEMU, Integer.MAX_VALUE);
            }
        }

        if (declaredWidth == widthInEMU ^ declaredHeight == heightInEMU) {
            if (declaredWidth == widthInEMU) {
                declaredWidth = (int) (declaredHeight * naturalAspect);
            } else {
                declaredHeight = (int) (declaredWidth / naturalAspect);
            }
        }

        // 计算尺寸
        int calculatedWidth, calculatedHeight;
        if (declaredWidth < maxWidthInEMU && declaredHeight <= maxHeightInEMU) {
            calculatedWidth = declaredWidth;
            calculatedHeight = declaredHeight;
        } else if (declaredWidth > maxWidthInEMU && declaredHeight <= maxHeightInEMU) {
            calculatedWidth = maxWidthInEMU;
            calculatedHeight = (int) (maxWidthInEMU / naturalAspect);
        } else if (declaredHeight > maxHeightInEMU && declaredWidth <= maxWidthInEMU) {
            calculatedHeight = maxHeightInEMU;
            calculatedWidth = (int) (maxHeightInEMU * naturalAspect);
        } else {
            float widthRatio = 1f * maxWidthInEMU / declaredWidth;
            float heightRatio = 1f * maxHeightInEMU / declaredHeight;
            float scale = Math.min(widthRatio, heightRatio);
            calculatedWidth = (int) (declaredWidth * scale);
            calculatedHeight = (int) (declaredHeight * scale);
        }

        context.renderPicture(inputStream, type, HtmlConstants.TAG_IMG,
            calculatedWidth, calculatedHeight, svgData);
    }

}


================================================
FILE: src/main/java/org/ddr/poi/html/tag/ItalicRenderer.java
================================================
/*
 * Copyright 2016 - 2021 Draco, https://github.com/draco1023
 *
 * 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
 *
 *     http://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.
 */

package org.ddr.poi.html.tag;

import org.ddr.poi.html.ElementRenderer;
import org.ddr.poi.html.HtmlConstants;
import org.ddr.poi.html.HtmlRenderContext;
import org.ddr.poi.html.util.CSSStyleUtils;
import org.jsoup.nodes.Element;

/**
 * 斜体标签渲染器
 *
 * @author Draco
 * @since 2021-02-23
 */
public class ItalicRenderer implements ElementRenderer {
    private static final String[] TAGS = {HtmlConstants.TAG_I, HtmlConstants.TAG_EM};

    /**
     * 开始渲染
     *
     * @param element HTML元素
     * @param context 渲染上下文
     * @return 是否继续渲染子元素
     */
    @Override
    public boolean renderStart(Element element, HtmlRenderContext context) {
        context.pushInlineStyle(CSSStyleUtils.parse(HtmlConstants.DEFINED_ITALIC), element.isBlock());
        return true;
    }

    /**
     * 元素渲染结束需要执行的逻辑
     *
     * @param element HTML元素
     * @param context 渲染上下文
     */
    @Override
    public void renderEnd(Element element, HtmlRenderContext context) {
        context.popInlineStyle();
    }

    @Override
    public String[] supportedTags() {
        return TAGS;
    }

    @Override
    public boolean renderAsBlock() {
        return false;
    }
}


================================================
FILE: src/main/java/org/ddr/poi/html/tag/LaTeXRenderer.java
================================================
/*
 * Copyright 2016 - 2021 Draco, https://github.com/draco1023
 *
 * 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
 *
 *     http://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.
 */

package org.ddr.poi.html.tag;

import org.apache.commons.lang3.StringUtils;
import org.ddr.poi.html.ElementRenderer;
import org.ddr.poi.html.HtmlConstants;
import org.ddr.poi.html.HtmlRenderContext;
import org.ddr.poi.latex.LaTeXUtils;
import org.jsoup.nodes.Element;
import uk.ac.ed.ph.snuggletex.SnuggleSession;

/**
 * latex标签渲染器(自定义)
 *
 * @author Draco
 * @since 2023-07-17
 */
public class LaTeXRenderer implements ElementRenderer {
    private static final String[] TAGS = {HtmlConstants.TAG_LATEX};

    /**
     * 开始渲染
     *
     * @param element HTML元素
     * @param context 渲染上下文
     * @return 是否继续渲染子元素
     */
    @Override
    public boolean renderStart(Element element, HtmlRenderContext context) {
        String latex = element.wholeText();
        if (StringUtils.isBlank(latex)) {
            return false;
        }

        SnuggleSession session = LaTeXUtils.createSession();
        LaTeXUtils.parse(session, latex);
        LaTeXUtils.renderTo(context.getClosestParagraph(), null, session, context.getMathRenderConfig());

        return false;
    }

    @Override
    public String[] supportedTags() {
        return TAGS;
    }

    @Override
    public boolean renderAsBlock() {
        return false;
    }

}


================================================
FILE: src/main/java/org/ddr/poi/html/tag/ListItemRenderer.java
================================================
/*
 * Copyright 2016 - 2021 Draco, https://github.com/draco1023
 *
 * 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
 *
 *     http://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.
 */

package org.ddr.poi.html.tag;

import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.ddr.poi.html.ElementRenderer;
import org.ddr.poi.html.HtmlConstants;
import org.ddr.poi.html.HtmlRenderContext;
import org.jsoup.nodes.Element;

/**
 * 列表项渲染器
 *
 * @author Draco
 * @since 2021-02-19
 */
public class ListItemRenderer implements ElementRenderer {
    private static final String[] TAGS = {HtmlConstants.TAG_LI};

    /**
     * 开始渲染
     *
     * @param element HTML元素
     * @param context 渲染上下文
     * @return 是否继续渲染子元素
     */
    @Override
    public boolean renderStart(Element element, HtmlRenderContext context) {
        XWPFParagraph paragraph = context.getClosestParagraph();
        context.markDedupe(paragraph);
        context.getNumberingContext().add(paragraph);
        return true;
    }

    /**
     * 元素渲染结束需要执行的逻辑
     *
     * @param element HTML元素
     * @param context 渲染上下文
     */
    @Override
    public void renderEnd(Element element, HtmlRenderContext context) {
        context.unmarkDedupe();
    }

    @Override
    public String[] supportedTags() {
        return TAGS;
    }

    @Override
    public boolean renderAsBlock() {
        return true;
    }
}


================================================
FILE: src/main/java/org/ddr/poi/html/tag/ListRenderer.java
================================================
/*
 * Copyright 2016 - 2021 Draco, https://github.com/draco1023
 *
 * 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
 *
 *     http://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.
 */

package org.ddr.poi.html.tag;

import org.apache.commons.lang3.StringUtils;
import org.ddr.poi.html.ElementRenderer;
import org.ddr.poi.html.HtmlConstants;
import org.ddr.poi.html.HtmlRenderContext;
import org.ddr.poi.html.util.CSSLength;
import org.ddr.poi.html.util.ListStyle;
import org.ddr.poi.html.util.ListStyleType;
import org.ddr.poi.html.util.RenderUtils;
import org.jsoup.nodes.Element;

/**
 * 列表渲染器
 *
 * @author Draco
 * @since 2021-02-18
 */
public class ListRenderer implements ElementRenderer {
    private static final String[] TAGS = {HtmlConstants.TAG_UL, HtmlConstants.TAG_OL};

    /**
     * 开始渲染
     *
     * @param element HTML元素
     * @param context 渲染上下文
     * @return 是否继续渲染子元素
     */
    @Override
    public boolean renderStart(Element element, HtmlRenderContext context) {
        String listStylePosition = context.currentElementStyle().getPropertyValue(HtmlConstants.CSS_LIST_STYLE_POSITION);
        boolean hanging = !HtmlConstants.INSIDE.equals(listStylePosition);
        CSSLength marginLeft = CSSLength.of(context.currentElementStyle().getMarginLeft().toLowerCase());
        int left = marginLeft.isValid() && !marginLeft.isPercent()
                ? RenderUtils.emuToTwips(context.lengthToEMU(marginLeft)) : 0;
        CSSLength marginRight = CSSLength.of(context.currentElementStyle().getMarginRight().toLowerCase());
        int right = marginRight.isValid() && !marginRight.isPercent()
                ? RenderUtils.emuToTwips(context.lengthToEMU(marginRight)) : 0;
        ListStyle listStyle = new ListStyle(determineNumberFormat(context, element), hanging, left, right);
        context.getNumberingContext().startLevel(listStyle);
        return true;
    }

    private ListStyleType determineNumberFormat(HtmlRenderContext context, Element element) {
        String listStyleType = context.currentElementStyle()
                .getPropertyValue(HtmlConstants.CSS_LIST_STYLE_TYPE).toLowerCase();
        ListStyleType format;
        switch (element.tag().normalName()) {
            case HtmlConstants.TAG_OL:
                if (StringUtils.isNotBlank(listStyleType)) {
                    format = ListStyleType.Ordered.of(listStyleType);
                } else {
                    // 支持ol的type属性
                    String type = element.attr(HtmlConstants.ATTR_TYPE);
                    format = ListStyleType.Ordered.of(type);
                }
                break;
            case HtmlConstants.TAG_UL:
                format = ListStyleType.Unordered.of(listStyleType);
                break;
            default:
                format = ListStyleType.Unordered.NONE;
        }
        return format;
    }

    /**
     * 元素渲染结束需要执行的逻辑
     *
     * @param element HTML元素
     * @param context 渲染上下文
     */
    @Override
    public void renderEnd(Element element, HtmlRenderContext context) {
        context.getNumberingContext().endLevel();
    }

    @Override
    public String[] supportedTags() {
        return TAGS;
    }

    @Override
    public boolean renderAsBlock() {
        // 列表标签本身不需要作为块状元素渲染,因为每一个列表项都是一个块状元素
        return false;
    }
}


================================================
FILE: src/main/java/org/ddr/poi/html/tag/MarkRenderer.java
================================================
/*
 * Copyright 2016 - 2022 Draco, https://github.com/draco1023
 *
 * 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
 *
 *     http://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.
 */

package org.ddr.poi.html.tag;

import com.steadystate.css.dom.CSSStyleDeclarationImpl;
import org.apache.commons.lang3.StringUtils;
import org.ddr.poi.html.ElementRenderer;
import org.ddr.poi.html.HtmlConstants;
import org.ddr.poi.html.HtmlRenderContext;
import org.jsoup.nodes.Element;

/**
 * 标记标签渲染器
 *
 * @author Draco
 * @since 2022-06-11
 */
public class MarkRenderer implements ElementRenderer {
    private static final String[] TAGS = {HtmlConstants.TAG_MARK};

    /**
     * 开始渲染
     *
     * @param element HTML元素
     * @param context 渲染上下文
     * @return 是否继续渲染子元素
     */
    @Override
    public boolean renderStart(Element element, HtmlRenderContext context) {
        CSSStyleDeclarationImpl cssStyleDeclaration = context.currentElementStyle();

        if (StringUtils.isEmpty(cssStyleDeclaration.getBackgroundColor())) {
            cssStyleDeclaration.setBackgroundColor("yellow");
        }
        return true;
    }

    @Override
    public String[] supportedTags() {
        return TAGS;
    }

    @Override
   
Download .txt
gitextract_tlgs8gmd/

├── .gitattributes
├── .github/
│   └── ISSUE_TEMPLATE/
│       └── bug_report.md
├── .gitignore
├── LICENSE
├── README.md
├── build.gradle
├── gradle/
│   └── wrapper/
│       └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── settings.gradle
└── src/
    ├── main/
    │   ├── java/
    │   │   └── org/
    │   │       ├── apache/
    │   │       │   └── poi/
    │   │       │       └── xwpf/
    │   │       │           └── usermodel/
    │   │       │               ├── SVGPictureData.java
    │   │       │               └── SVGRelation.java
    │   │       ├── ddr/
    │   │       │   ├── image/
    │   │       │   │   ├── ImageInfo.java
    │   │       │   │   ├── ImageInputStreamWrapper.java
    │   │       │   │   ├── ImageType.java
    │   │       │   │   ├── MetadataReader.java
    │   │       │   │   ├── MetadataReaders.java
    │   │       │   │   ├── avif/
    │   │       │   │   │   ├── AvifImageReader.java
    │   │       │   │   │   ├── AvifImageReaderSpi.java
    │   │       │   │   │   ├── AvifMetadataReader.java
    │   │       │   │   │   └── AvifProviderInfo.java
    │   │       │   │   ├── bmp/
    │   │       │   │   │   └── BmpMetadataReader.java
    │   │       │   │   ├── eps/
    │   │       │   │   │   └── EpsMetadataReader.java
    │   │       │   │   ├── gif/
    │   │       │   │   │   └── GifMetadataReader.java
    │   │       │   │   ├── heif/
    │   │       │   │   │   ├── HeifImageReader.java
    │   │       │   │   │   ├── HeifImageReaderSpi.java
    │   │       │   │   │   ├── HeifMetadataReader.java
    │   │       │   │   │   └── HeifProviderInfo.java
    │   │       │   │   ├── jpeg/
    │   │       │   │   │   └── JpegMetadataReader.java
    │   │       │   │   ├── png/
    │   │       │   │   │   └── PngMetadataReader.java
    │   │       │   │   ├── tiff/
    │   │       │   │   │   └── TiffMetadataReader.java
    │   │       │   │   └── webp/
    │   │       │   │       └── WebpMetadataReader.java
    │   │       │   └── poi/
    │   │       │       ├── html/
    │   │       │       │   ├── ElementRenderer.java
    │   │       │       │   ├── ElementRendererProvider.java
    │   │       │       │   ├── HtmlConstants.java
    │   │       │       │   ├── HtmlRenderConfig.java
    │   │       │       │   ├── HtmlRenderContext.java
    │   │       │       │   ├── HtmlRenderPolicy.java
    │   │       │       │   ├── tag/
    │   │       │       │   │   ├── ARenderer.java
    │   │       │       │   │   ├── BigRenderer.java
    │   │       │       │   │   ├── BoldRenderer.java
    │   │       │       │   │   ├── BreakRenderer.java
    │   │       │       │   │   ├── DeleteRenderer.java
    │   │       │       │   │   ├── FigureCaptionRenderer.java
    │   │       │       │   │   ├── FigureRenderer.java
    │   │       │       │   │   ├── HeaderBreakRenderer.java
    │   │       │       │   │   ├── HeaderRenderer.java
    │   │       │       │   │   ├── ImageRenderer.java
    │   │       │       │   │   ├── ItalicRenderer.java
    │   │       │       │   │   ├── LaTeXRenderer.java
    │   │       │       │   │   ├── ListItemRenderer.java
    │   │       │       │   │   ├── ListRenderer.java
    │   │       │       │   │   ├── MarkRenderer.java
    │   │       │       │   │   ├── MathRenderer.java
    │   │       │       │   │   ├── OmittedRenderer.java
    │   │       │       │   │   ├── PreRenderer.java
    │   │       │       │   │   ├── RubyRenderer.java
    │   │       │       │   │   ├── SmallRenderer.java
    │   │       │       │   │   ├── SubscriptRenderer.java
    │   │       │       │   │   ├── SuperscriptRenderer.java
    │   │       │       │   │   ├── SvgRenderer.java
    │   │       │       │   │   ├── TableCellRenderer.java
    │   │       │       │   │   ├── TableRenderer.java
    │   │       │       │   │   ├── UnderlineRenderer.java
    │   │       │       │   │   └── WalkThroughRenderer.java
    │   │       │       │   └── util/
    │   │       │       │       ├── BoxProperty.java
    │   │       │       │       ├── CSSLength.java
    │   │       │       │       ├── CSSLengthUnit.java
    │   │       │       │       ├── CSSStyleUtils.java
    │   │       │       │       ├── Colors.java
    │   │       │       │       ├── ColumnStyle.java
    │   │       │       │       ├── EmptyCSSStyle.java
    │   │       │       │       ├── InlineStyle.java
    │   │       │       │       ├── JsoupUtils.java
    │   │       │       │       ├── ListStyle.java
    │   │       │       │       ├── ListStyleType.java
    │   │       │       │       ├── NamedBorderWidth.java
    │   │       │       │       ├── NamedFontSize.java
    │   │       │       │       ├── NumberingContext.java
    │   │       │       │       ├── RenderUtils.java
    │   │       │       │       ├── Span.java
    │   │       │       │       ├── SpanWidth.java
    │   │       │       │       ├── WhiteSpaceRule.java
    │   │       │       │       └── XWPFParagraphRuns.java
    │   │       │       ├── latex/
    │   │       │       │   ├── LaTeXRenderPolicy.java
    │   │       │       │   ├── LaTeXUtils.java
    │   │       │       │   ├── TagHandler.java
    │   │       │       │   └── TextCircledHandler.java
    │   │       │       ├── math/
    │   │       │       │   ├── EmptyEOfNaryDisplayMode.java
    │   │       │       │   ├── MathMLRenderPolicy.java
    │   │       │       │   ├── MathMLUtils.java
    │   │       │       │   └── MathRenderConfig.java
    │   │       │       └── util/
    │   │       │           ├── ByteArrayCopyStream.java
    │   │       │           ├── HttpURLConnectionUtils.java
    │   │       │           └── XmlUtils.java
    │   │       └── jsoup/
    │   │           └── parser/
    │   │               └── CustomHtmlTreeBuilder.java
    │   └── resources/
    │       ├── META-INF/
    │       │   └── services/
    │       │       └── javax.imageio.spi.ImageReaderSpi
    │       ├── MML2OMML.XSL
    │       ├── math-character-aliases.txt
    │       └── math-character-circled.txt
    └── test/
        ├── java/
        │   └── org/
        │       └── ddr/
        │           └── poi/
        │               ├── FileReader.java
        │               ├── html/
        │               │   └── HtmlRenderPolicyTest.java
        │               ├── latex/
        │               │   └── LaTeXRenderPolicyTest.java
        │               └── math/
        │                   └── MathMLRenderPolicyTest.java
        └── resources/
            ├── 0.xml
            ├── 1.html
            ├── 1.xml
            ├── 2.html
            ├── 2.xml
            ├── 3.xml
            ├── math.docx
            ├── notes.docx
            ├── poi.docx
            └── simplelogger.properties
Download .txt
SYMBOL INDEX (681 symbols across 90 files)

FILE: src/main/java/org/apache/poi/xwpf/usermodel/SVGPictureData.java
  class SVGPictureData (line 11) | public class SVGPictureData extends XWPFPictureData {
    method SVGPictureData (line 15) | public SVGPictureData() {
    method SVGPictureData (line 18) | public SVGPictureData(PackagePart part) {
    method initRelation (line 22) | public static void initRelation() {

FILE: src/main/java/org/apache/poi/xwpf/usermodel/SVGRelation.java
  class SVGRelation (line 15) | public class SVGRelation extends POIXMLRelation {
    method SVGRelation (line 40) | private SVGRelation() {

FILE: src/main/java/org/ddr/image/ImageInfo.java
  class ImageInfo (line 6) | public class ImageInfo {
    method ImageInfo (line 11) | public ImageInfo(ByteArrayInputStream stream) {
    method ImageInfo (line 15) | public ImageInfo(ByteArrayInputStream stream, ImageType type, Dimensio...
    method getStream (line 21) | public ByteArrayInputStream getStream() {
    method setStream (line 25) | public void setStream(ByteArrayInputStream stream) {
    method getType (line 29) | public ImageType getType() {
    method setType (line 33) | public void setType(ImageType type) {
    method getDimension (line 37) | public Dimension getDimension() {
    method setDimension (line 41) | public void setDimension(Dimension dimension) {
    method getWidth (line 45) | public int getWidth() {
    method getHeight (line 49) | public int getHeight() {
    method getRawType (line 53) | public int getRawType() {

FILE: src/main/java/org/ddr/image/ImageInputStreamWrapper.java
  class ImageInputStreamWrapper (line 9) | public class ImageInputStreamWrapper extends InputStream implements Imag...
    method ImageInputStreamWrapper (line 13) | public ImageInputStreamWrapper(ImageInputStream input) {
    method setByteOrder (line 17) | @Override
    method getByteOrder (line 22) | @Override
    method read (line 27) | @Override
    method readBytes (line 32) | @Override
    method readBoolean (line 37) | @Override
    method readByte (line 42) | @Override
    method readUnsignedByte (line 47) | @Override
    method readShort (line 52) | @Override
    method readUnsignedShort (line 57) | @Override
    method readChar (line 62) | @Override
    method readInt (line 67) | @Override
    method readUnsignedInt (line 72) | @Override
    method readLong (line 77) | @Override
    method readFloat (line 82) | @Override
    method readDouble (line 87) | @Override
    method readLine (line 92) | @Override
    method readUTF (line 97) | @Override
    method readFully (line 102) | @Override
    method readFully (line 107) | @Override
    method readFully (line 112) | @Override
    method readFully (line 117) | @Override
    method readFully (line 122) | @Override
    method readFully (line 127) | @Override
    method readFully (line 132) | @Override
    method readFully (line 137) | @Override
    method getStreamPosition (line 142) | @Override
    method getBitOffset (line 147) | @Override
    method setBitOffset (line 152) | @Override
    method readBit (line 157) | @Override
    method readBits (line 162) | @Override
    method length (line 167) | @Override
    method skipBytes (line 172) | @Override
    method skipBytes (line 177) | @Override
    method seek (line 182) | @Override
    method mark (line 187) | @Override
    method flushBefore (line 192) | @Override
    method flush (line 197) | @Override
    method getFlushedPosition (line 202) | @Override
    method isCached (line 207) | @Override
    method isCachedMemory (line 212) | @Override
    method isCachedFile (line 217) | @Override

FILE: src/main/java/org/ddr/image/ImageType.java
  type ImageType (line 5) | public enum ImageType {
    method ImageType (line 22) | ImageType(int type) {
    method getExtension (line 26) | public String getExtension() {
    method getType (line 30) | public int getType() {

FILE: src/main/java/org/ddr/image/MetadataReader.java
  type MetadataReader (line 8) | public interface MetadataReader {
    method canRead (line 9) | boolean canRead(FileType type);
    method getType (line 11) | ImageType getType(Metadata metadata);
    method getDimension (line 13) | Dimension getDimension(Metadata metadata);

FILE: src/main/java/org/ddr/image/MetadataReaders.java
  class MetadataReaders (line 13) | public class MetadataReaders {

FILE: src/main/java/org/ddr/image/avif/AvifImageReader.java
  class AvifImageReader (line 7) | public class AvifImageReader extends HeifImageReader {
    method AvifImageReader (line 9) | public AvifImageReader(ImageReaderSpi provider) {

FILE: src/main/java/org/ddr/image/avif/AvifImageReaderSpi.java
  class AvifImageReaderSpi (line 13) | public final class AvifImageReaderSpi extends ImageReaderSpiBase {
    method AvifImageReaderSpi (line 25) | public AvifImageReaderSpi() {
    method canDecodeInput (line 29) | @Override
    method canDecode (line 34) | private boolean canDecode(ImageInputStream input) throws IOException {
    method createReaderInstance (line 55) | @Override
    method getDescription (line 60) | @Override

FILE: src/main/java/org/ddr/image/avif/AvifMetadataReader.java
  class AvifMetadataReader (line 6) | public class AvifMetadataReader extends HeifMetadataReader {
    method canRead (line 7) | @Override

FILE: src/main/java/org/ddr/image/avif/AvifProviderInfo.java
  class AvifProviderInfo (line 5) | final class AvifProviderInfo extends ReaderWriterProviderInfo {
    method AvifProviderInfo (line 6) | AvifProviderInfo() {

FILE: src/main/java/org/ddr/image/bmp/BmpMetadataReader.java
  class BmpMetadataReader (line 12) | public class BmpMetadataReader implements MetadataReader {
    method canRead (line 13) | @Override
    method getType (line 18) | @Override
    method getDimension (line 23) | @Override

FILE: src/main/java/org/ddr/image/eps/EpsMetadataReader.java
  class EpsMetadataReader (line 12) | public class EpsMetadataReader implements MetadataReader{
    method canRead (line 13) | @Override
    method getType (line 18) | @Override
    method getDimension (line 23) | @Override

FILE: src/main/java/org/ddr/image/gif/GifMetadataReader.java
  class GifMetadataReader (line 12) | public class GifMetadataReader implements MetadataReader{
    method canRead (line 13) | @Override
    method getType (line 18) | @Override
    method getDimension (line 23) | @Override

FILE: src/main/java/org/ddr/image/heif/HeifImageReader.java
  class HeifImageReader (line 31) | public class HeifImageReader extends ImageReaderBase {
    method HeifImageReader (line 40) | public HeifImageReader(ImageReaderSpi provider) {
    method HeifImageReader (line 44) | protected HeifImageReader(ImageReaderSpi provider, MetadataReader meta...
    method resetMembers (line 50) | @Override
    method setInput (line 56) | @Override
    method getWidth (line 71) | @Override
    method getHeight (line 76) | @Override
    method getImageTypes (line 81) | @Override
    method read (line 86) | @Override
    method convert (line 91) | BufferedImage convert(ImageReadParam param) {

FILE: src/main/java/org/ddr/image/heif/HeifImageReaderSpi.java
  class HeifImageReaderSpi (line 13) | public final class HeifImageReaderSpi extends ImageReaderSpiBase {
    method HeifImageReaderSpi (line 25) | public HeifImageReaderSpi() {
    method canDecodeInput (line 29) | @Override
    method canDecode (line 34) | private boolean canDecode(ImageInputStream input) throws IOException {
    method createReaderInstance (line 58) | @Override
    method getDescription (line 63) | @Override

FILE: src/main/java/org/ddr/image/heif/HeifMetadataReader.java
  class HeifMetadataReader (line 16) | public class HeifMetadataReader implements MetadataReader {
    method canRead (line 17) | @Override
    method getType (line 22) | @Override
    method getDimension (line 32) | @Override

FILE: src/main/java/org/ddr/image/heif/HeifProviderInfo.java
  class HeifProviderInfo (line 5) | final class HeifProviderInfo extends ReaderWriterProviderInfo {
    method HeifProviderInfo (line 6) | HeifProviderInfo() {

FILE: src/main/java/org/ddr/image/jpeg/JpegMetadataReader.java
  class JpegMetadataReader (line 12) | public class JpegMetadataReader implements MetadataReader {
    method canRead (line 13) | @Override
    method getType (line 18) | @Override
    method getDimension (line 23) | @Override

FILE: src/main/java/org/ddr/image/png/PngMetadataReader.java
  class PngMetadataReader (line 13) | public class PngMetadataReader implements MetadataReader {
    method canRead (line 14) | @Override
    method getType (line 19) | @Override
    method getDimension (line 24) | @Override

FILE: src/main/java/org/ddr/image/tiff/TiffMetadataReader.java
  class TiffMetadataReader (line 13) | public class TiffMetadataReader implements MetadataReader {
    method canRead (line 16) | @Override
    method getType (line 21) | @Override
    method getDimension (line 26) | @Override

FILE: src/main/java/org/ddr/image/webp/WebpMetadataReader.java
  class WebpMetadataReader (line 12) | public class WebpMetadataReader implements MetadataReader{
    method canRead (line 13) | @Override
    method getType (line 18) | @Override
    method getDimension (line 31) | @Override

FILE: src/main/java/org/ddr/poi/html/ElementRenderer.java
  type ElementRenderer (line 28) | public interface ElementRenderer {
    method renderStart (line 36) | boolean renderStart(Element element, HtmlRenderContext context);
    method renderEnd (line 44) | default void renderEnd(Element element, HtmlRenderContext context) {
    method supportedTags (line 50) | String[] supportedTags();
    method renderAsBlock (line 55) | boolean renderAsBlock();

FILE: src/main/java/org/ddr/poi/html/ElementRendererProvider.java
  type ElementRendererProvider (line 25) | @FunctionalInterface
    method get (line 33) | ElementRenderer get(String tagNormalName);

FILE: src/main/java/org/ddr/poi/html/HtmlConstants.java
  type HtmlConstants (line 29) | public interface HtmlConstants {
    method inlineStyle (line 372) | static String inlineStyle(String key, String value) {
    method isMajorFont (line 380) | static boolean isMajorFont(String fontName) {

FILE: src/main/java/org/ddr/poi/html/HtmlRenderConfig.java
  class HtmlRenderConfig (line 29) | public class HtmlRenderConfig {
    method getGlobalFont (line 47) | public String getGlobalFont() {
    method setGlobalFont (line 51) | public void setGlobalFont(String globalFont) {
    method getGlobalFontSize (line 58) | public CSSLength getGlobalFontSize() {
    method setGlobalFontSize (line 62) | public void setGlobalFontSize(CSSLength globalFontSize) {
    method getGlobalFontSizeInHalfPoints (line 67) | public int getGlobalFontSizeInHalfPoints() {
    method isShowDefaultTableBorderInTableCell (line 74) | public boolean isShowDefaultTableBorderInTableCell() {
    method setShowDefaultTableBorderInTableCell (line 78) | public void setShowDefaultTableBorderInTableCell(boolean showDefaultTa...
    method getCustomRenderers (line 85) | public List<ElementRenderer> getCustomRenderers() {
    method setCustomRenderers (line 89) | public void setCustomRenderers(List<ElementRenderer> customRenderers) {
    method getNumberingIndent (line 96) | public int getNumberingIndent() {
    method setNumberingIndent (line 100) | public void setNumberingIndent(int numberingIndent) {
    method getNumberingHanging (line 107) | public int getNumberingHanging() {
    method setNumberingHanging (line 111) | public void setNumberingHanging(int numberingHanging) {
    method getNumberingSpacing (line 118) | public STLevelSuffix.Enum getNumberingSpacing() {
    method setNumberingSpacing (line 122) | public void setNumberingSpacing(STLevelSuffix.Enum numberingSpacing) {
    method getMathRenderConfig (line 126) | public MathRenderConfig getMathRenderConfig() {

FILE: src/main/java/org/ddr/poi/html/HtmlRenderContext.java
  class HtmlRenderContext (line 123) | public class HtmlRenderContext extends RenderContext<String> {
    method HtmlRenderContext (line 253) | public HtmlRenderContext(RenderContext<String> context, ElementRendere...
    method extractPlaceholderStyle (line 302) | private void extractPlaceholderStyle() {
    method getContainer (line 341) | @Override
    method pushContainer (line 352) | public void pushContainer(IBody body) {
    method popContainer (line 359) | public void popContainer() {
    method getClosestParagraph (line 368) | public XWPFParagraph getClosestParagraph() {
    method startHyperlink (line 401) | public void startHyperlink(String uri) {
    method endHyperlink (line 432) | public void endHyperlink() {
    method newParagraph (line 443) | public XWPFParagraph newParagraph(IBody container, XmlCursor cursor) {
    method adjustPicture (line 457) | private void adjustPicture() {
    method newRun (line 503) | public CTR newRun() {
    method initHyperlinkStyle (line 544) | private void initHyperlinkStyle(CTR ctr) {
    method getClosestTable (line 558) | public XWPFTable getClosestTable() {
    method pushInlineStyle (line 582) | public void pushInlineStyle(CSSStyleDeclarationImpl inlineStyle, boole...
    method popInlineStyle (line 649) | public void popInlineStyle() {
    method currentElementStyle (line 659) | public CSSStyleDeclarationImpl currentElementStyle() {
    method getPropertyValue (line 670) | public String getPropertyValue(String property) {
    method getPropertyValue (line 681) | public String getPropertyValue(String property, boolean inlineOnly) {
    method getPropertyValue (line 693) | public String getPropertyValue(String property, boolean caseSensitive,...
    method getDefaultFontSize (line 709) | public CSSLength getDefaultFontSize() {
    method getInheritedFontSizeInHalfPoints (line 716) | public int getInheritedFontSizeInHalfPoints() {
    method getAvailableWidthInEMU (line 728) | public int getAvailableWidthInEMU() {
    method computeLengthInEMU (line 746) | public int computeLengthInEMU(String length, String maxLength, int nat...
    method computeLengthInEMU (line 775) | public int computeLengthInEMU(CSSLength cssLength, int naturalEMU, int...
    method renderText (line 794) | public void renderText(String text) {
    method addText (line 947) | private void addText(CTR ctr, StringBuilder sb, boolean endTrimmed) {
    method applyTextStyle (line 965) | private void applyTextStyle(CTR ctr) {
    method renderPicture (line 1078) | public void renderPicture(InputStream pictureData, int pictureType, St...
    method attachSvgData (line 1155) | private void attachSvgData(XWPFPicture xwpfPicture, byte[] svgData) th...
    method lengthToEMU (line 1178) | public int lengthToEMU(CSSLength length) {
    method getNumberingContext (line 1212) | public NumberingContext getNumberingContext() {
    method getPageWidth (line 1216) | public CSSLength getPageWidth() {
    method getPageHeight (line 1220) | public CSSLength getPageHeight() {
    method getMarginTop (line 1224) | public CSSLength getMarginTop() {
    method getMarginRight (line 1228) | public CSSLength getMarginRight() {
    method getMarginBottom (line 1232) | public CSSLength getMarginBottom() {
    method getMarginLeft (line 1236) | public CSSLength getMarginLeft() {
    method getAvailablePageWidth (line 1240) | public int getAvailablePageWidth() {
    method getAvailablePageHeight (line 1244) | public int getAvailablePageHeight() {
    method getMathRenderConfig (line 1248) | public MathRenderConfig getMathRenderConfig() {
    method setMathRenderConfig (line 1252) | public void setMathRenderConfig(MathRenderConfig mathRenderConfig) {
    method getCurrentRun (line 1256) | public XWPFRun getCurrentRun() {
    method getGlobalFont (line 1260) | public String getGlobalFont() {
    method getGlobalFontSize (line 1264) | public BigInteger getGlobalFontSize() {
    method isShowDefaultTableBorderInTableCell (line 1268) | public boolean isShowDefaultTableBorderInTableCell() {
    method setShowDefaultTableBorderInTableCell (line 1272) | public void setShowDefaultTableBorderInTableCell(boolean showDefaultTa...
    method setGlobalFont (line 1276) | public void setGlobalFont(String globalFont) {
    method setGlobalFontSize (line 1280) | public void setGlobalFontSize(BigInteger globalFontSize) {
    method isBlocked (line 1284) | public boolean isBlocked() {
    method incrementBlockLevel (line 1288) | public void incrementBlockLevel() {
    method decrementBlockLevel (line 1292) | public void decrementBlockLevel() {
    method renderDocument (line 1296) | public void renderDocument(Document document) {
    method renderNode (line 1311) | public void renderNode(Node node) {
    method renderElement (line 1322) | public void renderElement(Element element) {
    method isEmptyParagraph (line 1410) | private boolean isEmptyParagraph(XWPFParagraph paragraph) {
    method removeParagraph (line 1423) | private void removeParagraph(IBody container, XWPFParagraph paragraph) {
    method renderAsBlock (line 1457) | public boolean renderAsBlock(Element element) {
    method renderAsBlock (line 1468) | private boolean renderAsBlock(Element element, ElementRenderer element...
    method shouldNewParagraph (line 1472) | private boolean shouldNewParagraph(Element element) {
    method removeLastBrRun (line 1495) | private boolean removeLastBrRun(XmlCursor xmlCursor) {
    method adjustCursor (line 1513) | private void adjustCursor(IBody container, boolean isTableTag) {
    method renderElementEnd (line 1533) | private void renderElementEnd(Element element, HtmlRenderContext conte...
    method moveContentToNewPrevParagraph (line 1543) | private void moveContentToNewPrevParagraph(CTR ctr) {
    method getCssStyleDeclaration (line 1608) | public CSSStyleDeclarationImpl getCssStyleDeclaration(Element element) {
    method pushCursor (line 1620) | public void pushCursor(XmlCursor targetCursor) {
    method popCursor (line 1630) | public boolean popCursor() {
    method currentCursorObject (line 1637) | public XmlObject currentCursorObject() {
    method markDedupe (line 1646) | public void markDedupe(XWPFParagraph paragraph) {
    method unmarkDedupe (line 1653) | public void unmarkDedupe() {
    class TextWrapper (line 1660) | private static class TextWrapper {
      method TextWrapper (line 1664) | public TextWrapper(CTText text, boolean endTrimmed) {
      method getText (line 1669) | public CTText getText() {
      method isEndTrimmed (line 1673) | public boolean isEndTrimmed() {

FILE: src/main/java/org/ddr/poi/html/HtmlRenderPolicy.java
  class HtmlRenderPolicy (line 73) | public class HtmlRenderPolicy extends AbstractRenderPolicy<String> {
    method HtmlRenderPolicy (line 77) | public HtmlRenderPolicy() {
    method HtmlRenderPolicy (line 81) | @Deprecated
    method HtmlRenderPolicy (line 88) | public HtmlRenderPolicy(HtmlRenderConfig config) {
    method getConfig (line 135) | public HtmlRenderConfig getConfig() {
    method validate (line 139) | @Override
    method doRender (line 144) | @Override
    method afterRender (line 163) | @Override
    method hasSibling (line 178) | private boolean hasSibling(XWPFRun run) {
    method isValidSibling (line 202) | private boolean isValidSibling(XmlCursor cursor) {

FILE: src/main/java/org/ddr/poi/html/tag/ARenderer.java
  class ARenderer (line 30) | public class ARenderer implements ElementRenderer {
    method renderStart (line 40) | @Override
    method renderEnd (line 53) | @Override
    method supportedTags (line 58) | @Override
    method renderAsBlock (line 63) | @Override

FILE: src/main/java/org/ddr/poi/html/tag/BigRenderer.java
  class BigRenderer (line 31) | @Deprecated
    method renderStart (line 42) | @Override
    method renderEnd (line 54) | @Override
    method supportedTags (line 59) | @Override
    method renderAsBlock (line 64) | @Override

FILE: src/main/java/org/ddr/poi/html/tag/BoldRenderer.java
  class BoldRenderer (line 31) | public class BoldRenderer implements ElementRenderer {
    method renderStart (line 41) | @Override
    method renderEnd (line 53) | @Override
    method supportedTags (line 58) | @Override
    method renderAsBlock (line 63) | @Override

FILE: src/main/java/org/ddr/poi/html/tag/BreakRenderer.java
  class BreakRenderer (line 33) | public class BreakRenderer implements ElementRenderer {
    method renderStart (line 43) | @Override
    method supportedTags (line 54) | @Override
    method renderAsBlock (line 59) | @Override

FILE: src/main/java/org/ddr/poi/html/tag/DeleteRenderer.java
  class DeleteRenderer (line 31) | public class DeleteRenderer implements ElementRenderer {
    method renderStart (line 41) | @Override
    method renderEnd (line 53) | @Override
    method supportedTags (line 58) | @Override
    method renderAsBlock (line 63) | @Override

FILE: src/main/java/org/ddr/poi/html/tag/FigureCaptionRenderer.java
  class FigureCaptionRenderer (line 14) | public class FigureCaptionRenderer implements ElementRenderer {
    method renderStart (line 24) | @Override
    method renderEnd (line 36) | @Override
    method supportedTags (line 44) | @Override
    method renderAsBlock (line 52) | @Override

FILE: src/main/java/org/ddr/poi/html/tag/FigureRenderer.java
  class FigureRenderer (line 19) | public class FigureRenderer implements ElementRenderer {
    method renderStart (line 29) | @Override
    method renderEnd (line 63) | @Override
    method supportedTags (line 71) | @Override
    method renderAsBlock (line 79) | @Override

FILE: src/main/java/org/ddr/poi/html/tag/HeaderBreakRenderer.java
  class HeaderBreakRenderer (line 37) | public class HeaderBreakRenderer implements ElementRenderer {
    method renderStart (line 55) | @Override
    method renderEnd (line 71) | @Override
    method supportedTags (line 76) | @Override
    method renderAsBlock (line 81) | @Override

FILE: src/main/java/org/ddr/poi/html/tag/HeaderRenderer.java
  class HeaderRenderer (line 36) | public class HeaderRenderer implements ElementRenderer {
    method renderStart (line 54) | @Override
    method renderEnd (line 73) | @Override
    method supportedTags (line 78) | @Override
    method renderAsBlock (line 83) | @Override

FILE: src/main/java/org/ddr/poi/html/tag/ImageRenderer.java
  class ImageRenderer (line 65) | public class ImageRenderer implements ElementRenderer {
    method renderStart (line 85) | @Override
    method handleData (line 106) | private void handleData(Element element, HtmlRenderContext context, St...
    method typeOf (line 170) | protected ImageType typeOf(BufferedImage image) {
    method handleRemoteImage (line 181) | private void handleRemoteImage(Element element, HtmlRenderContext cont...
    method analyzeImage (line 210) | private ImageInfo analyzeImage(ByteArrayInputStream inputStream, boole...
    method supportedTags (line 273) | @Override
    method renderAsBlock (line 278) | @Override
    method addPicture (line 294) | protected void addPicture(Element element, HtmlRenderContext context, ...

FILE: src/main/java/org/ddr/poi/html/tag/ItalicRenderer.java
  class ItalicRenderer (line 31) | public class ItalicRenderer implements ElementRenderer {
    method renderStart (line 41) | @Override
    method renderEnd (line 53) | @Override
    method supportedTags (line 58) | @Override
    method renderAsBlock (line 63) | @Override

FILE: src/main/java/org/ddr/poi/html/tag/LaTeXRenderer.java
  class LaTeXRenderer (line 33) | public class LaTeXRenderer implements ElementRenderer {
    method renderStart (line 43) | @Override
    method supportedTags (line 57) | @Override
    method renderAsBlock (line 62) | @Override

FILE: src/main/java/org/ddr/poi/html/tag/ListItemRenderer.java
  class ListItemRenderer (line 31) | public class ListItemRenderer implements ElementRenderer {
    method renderStart (line 41) | @Override
    method renderEnd (line 55) | @Override
    method supportedTags (line 60) | @Override
    method renderAsBlock (line 65) | @Override

FILE: src/main/java/org/ddr/poi/html/tag/ListRenderer.java
  class ListRenderer (line 35) | public class ListRenderer implements ElementRenderer {
    method renderStart (line 45) | @Override
    method determineNumberFormat (line 60) | private ListStyleType determineNumberFormat(HtmlRenderContext context,...
    method renderEnd (line 89) | @Override
    method supportedTags (line 94) | @Override
    method renderAsBlock (line 99) | @Override

FILE: src/main/java/org/ddr/poi/html/tag/MarkRenderer.java
  class MarkRenderer (line 32) | public class MarkRenderer implements ElementRenderer {
    method renderStart (line 42) | @Override
    method supportedTags (line 52) | @Override
    method renderAsBlock (line 57) | @Override

FILE: src/main/java/org/ddr/poi/html/tag/MathRenderer.java
  class MathRenderer (line 33) | public class MathRenderer implements ElementRenderer {
    method renderStart (line 43) | @Override
    method supportedTags (line 55) | @Override
    method renderAsBlock (line 60) | @Override

FILE: src/main/java/org/ddr/poi/html/tag/OmittedRenderer.java
  class OmittedRenderer (line 30) | public class OmittedRenderer implements ElementRenderer {
    method renderStart (line 52) | @Override
    method supportedTags (line 57) | @Override
    method renderAsBlock (line 62) | @Override

FILE: src/main/java/org/ddr/poi/html/tag/PreRenderer.java
  class PreRenderer (line 15) | public class PreRenderer implements ElementRenderer {
    method renderStart (line 18) | @Override
    method renderEnd (line 24) | @Override
    method supportedTags (line 29) | @Override
    method renderAsBlock (line 34) | @Override

FILE: src/main/java/org/ddr/poi/html/tag/RubyRenderer.java
  class RubyRenderer (line 40) | public class RubyRenderer implements ElementRenderer {
    method renderStart (line 45) | @Override
    method supportedTags (line 92) | @Override
    method renderAsBlock (line 97) | @Override

FILE: src/main/java/org/ddr/poi/html/tag/SmallRenderer.java
  class SmallRenderer (line 31) | public class SmallRenderer implements ElementRenderer {
    method renderStart (line 41) | @Override
    method renderEnd (line 53) | @Override
    method supportedTags (line 58) | @Override
    method renderAsBlock (line 63) | @Override

FILE: src/main/java/org/ddr/poi/html/tag/SubscriptRenderer.java
  class SubscriptRenderer (line 31) | public class SubscriptRenderer implements ElementRenderer {
    method renderStart (line 41) | @Override
    method renderEnd (line 53) | @Override
    method supportedTags (line 58) | @Override
    method renderAsBlock (line 63) | @Override

FILE: src/main/java/org/ddr/poi/html/tag/SuperscriptRenderer.java
  class SuperscriptRenderer (line 31) | public class SuperscriptRenderer implements ElementRenderer {
    method renderStart (line 41) | @Override
    method renderEnd (line 53) | @Override
    method supportedTags (line 58) | @Override
    method renderAsBlock (line 63) | @Override

FILE: src/main/java/org/ddr/poi/html/tag/SvgRenderer.java
  class SvgRenderer (line 25) | public class SvgRenderer extends ImageRenderer {
    method renderStart (line 41) | @Override
    method supportedTags (line 72) | @Override

FILE: src/main/java/org/ddr/poi/html/tag/TableCellRenderer.java
  class TableCellRenderer (line 40) | public class TableCellRenderer implements ElementRenderer {
    method renderStart (line 50) | @Override
    method renderEnd (line 75) | @Override
    method supportedTags (line 91) | @Override
    method renderAsBlock (line 96) | @Override

FILE: src/main/java/org/ddr/poi/html/tag/TableRenderer.java
  class TableRenderer (line 72) | public class TableRenderer implements ElementRenderer {
    method TableRenderer (line 77) | public TableRenderer() {
    method renderStart (line 94) | @Override
    method reorderTableChildren (line 382) | private void reorderTableChildren(Element element) {
    method handleTableProperties (line 399) | private void handleTableProperties(Element element, CTTbl ctTbl) {
    method extractColumnStyles (line 520) | private List<ColumnStyle> extractColumnStyles(Element colgroup) {
    method sumColumnWidths (line 552) | private CSSLength sumColumnWidths(List<ColumnStyle> columnWidths, int ...
    method renderCaption (line 583) | private void renderCaption(HtmlRenderContext context, XWPFTable table,...
    method renderEnd (line 627) | @Override
    method addVMergeCell (line 641) | private void addVMergeCell(HtmlRenderContext context, XWPFTableRow row...
    method createCell (line 658) | private XWPFTableCell createCell(XWPFTableRow row, int c) {
    method createRow (line 669) | private XWPFTableRow createRow(XWPFTable table, int r) {
    method supportedTags (line 674) | @Override
    method renderAsBlock (line 679) | @Override

FILE: src/main/java/org/ddr/poi/html/tag/UnderlineRenderer.java
  class UnderlineRenderer (line 31) | public class UnderlineRenderer implements ElementRenderer {
    method renderStart (line 41) | @Override
    method renderEnd (line 53) | @Override
    method supportedTags (line 58) | @Override
    method renderAsBlock (line 63) | @Override

FILE: src/main/java/org/ddr/poi/html/tag/WalkThroughRenderer.java
  class WalkThroughRenderer (line 30) | public class WalkThroughRenderer implements ElementRenderer {
    method renderStart (line 47) | @Override
    method supportedTags (line 52) | @Override
    method renderAsBlock (line 57) | @Override

FILE: src/main/java/org/ddr/poi/html/util/BoxProperty.java
  type BoxProperty (line 30) | public enum BoxProperty {
    method BoxProperty (line 47) | BoxProperty(String top, String right, String bottom, String left) {
    method setValues (line 54) | public void setValues(CSSStyleDeclarationImpl cssStyleDeclaration, int i,
    method setValues (line 62) | public void setValues(CSSStyleDeclarationImpl cssStyleDeclaration, int...
    method setValues (line 66) | public void setValues(CSSStyleDeclarationImpl cssStyleDeclaration, int...
    method setValues (line 70) | public void setValues(CSSStyleDeclarationImpl cssStyleDeclaration, int...

FILE: src/main/java/org/ddr/poi/html/util/CSSLength.java
  class CSSLength (line 31) | public class CSSLength {
    method CSSLength (line 44) | public CSSLength(double value, CSSLengthUnit unit) {
    method getValue (line 49) | public double getValue() {
    method getUnit (line 53) | public CSSLengthUnit getUnit() {
    method toString (line 57) | @Override
    method toEMU (line 65) | public int toEMU() {
    method unitValue (line 71) | public double unitValue() {
    method requireAbsoluteUnit (line 75) | private void requireAbsoluteUnit() {
    method validate (line 81) | private void validate() {
    method to (line 87) | public CSSLength to(CSSLengthUnit other) {
    method toHalfPoints (line 92) | public int toHalfPoints() {
    method isValid (line 97) | public boolean isValid() {
    method isPercent (line 101) | public boolean isPercent() {
    method isValidPercent (line 105) | public boolean isValidPercent() {
    method of (line 109) | public static CSSLength of(String text) {
    method equals (line 120) | @Override
    method hashCode (line 130) | @Override

FILE: src/main/java/org/ddr/poi/html/util/CSSLengthUnit.java
  type CSSLengthUnit (line 34) | public enum CSSLengthUnit {
    method CSSLengthUnit (line 79) | CSSLengthUnit(String literal, boolean system, boolean relative, boolea...
    method getLiteral (line 89) | public String getLiteral() {
    method isSystem (line 93) | public boolean isSystem() {
    method isRelative (line 97) | public boolean isRelative() {
    method isRelativeToParent (line 101) | public boolean isRelativeToParent() {
    method toString (line 105) | @Override
    method absoluteFactor (line 110) | public double absoluteFactor() {
    method to (line 114) | public double to(CSSLengthUnit other) {
    method of (line 131) | public static CSSLengthUnit of(String literal) {

FILE: src/main/java/org/ddr/poi/html/util/CSSStyleUtils.java
  class CSSStyleUtils (line 25) | public class CSSStyleUtils {
    method isEmpty (line 39) | public static boolean isEmpty(CSSStyleDeclarationImpl style) {
    method newParser (line 46) | public static CSSOMParser newParser() {
    method parse (line 56) | public static CSSStyleDeclarationImpl parse(String inlineStyle) {
    method parseValue (line 74) | public static CSSValue parseValue(String value) {
    method newProperty (line 90) | public static Property newProperty(String key, String value) {
    method split (line 99) | public static void split(CSSStyleDeclarationImpl style) {
    method splitBackground (line 159) | private static void splitBackground(CSSValueImpl valueList, int length...
    method splitBorder (line 179) | private static void splitBorder(CSSValueImpl valueList, int length, CS...
    method splitBorder (line 194) | private static void splitBorder(CSSValueImpl valueList, int length, CS...
    method handleBorderValue (line 210) | private static void handleBorderValue(CSSStyleDeclarationImpl style, i...
    method handleBorderValue (line 226) | private static void handleBorderValue(CSSStyleDeclarationImpl style, i...
    method splitFont (line 243) | private static void splitFont(CSSValueImpl valueList, int length, CSSS...
    method splitBox (line 302) | private static void splitBox(CSSValueImpl valueList, int length, CSSSt...
    method splitListStyle (line 324) | private static void splitListStyle(CSSValueImpl valueList, int length,...
    method handleListStyleValue (line 342) | private static void handleListStyleValue(CSSStyleDeclarationImpl style...
    method splitTextDecoration (line 350) | private static void splitTextDecoration(CSSValueImpl valueList, int le...

FILE: src/main/java/org/ddr/poi/html/util/Colors.java
  class Colors (line 33) | public class Colors {
    method getColorByName (line 199) | public static String getColorByName(String name) {
    method getColorByName (line 210) | public static String getColorByName(String name, String defaultColor) {
    method fromHSL (line 222) | public static String fromHSL(float h, float s, float l) {
    method hsl2rgb (line 227) | private static float[] hsl2rgb(float h, float s, float l) {
    method hue2rgb (line 241) | private static float hue2rgb(float h, float s, float l, int n) {
    method fromHWB (line 255) | public static String fromHWB(float h, float w, float b) {
    method toInt (line 270) | private static int toInt(float f) {
    method degrees (line 274) | private static float degrees(String s) {
    method toHexString (line 296) | public static String toHexString(int r, int g, int b) {
    method fromStyle (line 307) | public static String fromStyle(String style, String defaultColor) {
    method fromStyle (line 392) | public static String fromStyle(String style) {
    method warn (line 396) | private static void warn(String style) {
    method maybe (line 406) | public static boolean maybe(String style) {

FILE: src/main/java/org/ddr/poi/html/util/ColumnStyle.java
  class ColumnStyle (line 11) | public class ColumnStyle {
    method ColumnStyle (line 15) | public ColumnStyle(CSSStyleDeclarationImpl style, CSSLength width) {
    method getStyle (line 20) | public CSSStyleDeclarationImpl getStyle() {
    method getWidth (line 24) | public CSSLength getWidth() {

FILE: src/main/java/org/ddr/poi/html/util/EmptyCSSStyle.java
  class EmptyCSSStyle (line 16) | public class EmptyCSSStyle extends CSSStyleDeclarationImpl {
    method setProperties (line 17) | @Override
    method getProperties (line 22) | @Override
    method setProperty (line 27) | @Override
    method setCssText (line 32) | @Override

FILE: src/main/java/org/ddr/poi/html/util/InlineStyle.java
  class InlineStyle (line 27) | public final class InlineStyle {
    method InlineStyle (line 37) | public InlineStyle(CSSStyleDeclarationImpl declaration, boolean block) {
    method getDeclaration (line 42) | public CSSStyleDeclarationImpl getDeclaration() {
    method isBlock (line 46) | public boolean isBlock() {

FILE: src/main/java/org/ddr/poi/html/util/JsoupUtils.java
  class JsoupUtils (line 39) | public class JsoupUtils {
    method selectChildren (line 47) | public static void selectChildren(Elements collection, Element parent,...
    method children (line 65) | public static Elements children(Element parent, String tag) {
    method children (line 78) | public static Elements children(Element parent, String... tags) {
    method firstChild (line 92) | public static Element firstChild(Element parent, String tag) {
    method childRows (line 110) | public static Elements childRows(Element parent) {
    method parse (line 131) | public static Document parse(String html) {

FILE: src/main/java/org/ddr/poi/html/util/ListStyle.java
  class ListStyle (line 9) | public class ListStyle {
    method ListStyle (line 18) | public ListStyle(ListStyleType numberFormat, boolean hanging, int left...
    method getNumberFormat (line 25) | public ListStyleType getNumberFormat() {
    method isHanging (line 29) | public boolean isHanging() {
    method getLeft (line 33) | public int getLeft() {
    method getRight (line 37) | public int getRight() {
    method toString (line 41) | @Override

FILE: src/main/java/org/ddr/poi/html/util/ListStyleType.java
  type ListStyleType (line 16) | public interface ListStyleType {
    method getName (line 26) | String getName();
    method getFormat (line 28) | STNumberFormat.Enum getFormat();
    method getText (line 33) | String getText();
    method getFont (line 35) | String getFont();
    type Unordered (line 37) | enum Unordered implements ListStyleType {
      method Unordered (line 54) | Unordered(String name, STNumberFormat.Enum format, String text, Stri...
      method getName (line 61) | @Override
      method getFormat (line 66) | @Override
      method getText (line 71) | @Override
      method getFont (line 76) | @Override
      method of (line 81) | public static ListStyleType of(String type) {
    type Ordered (line 89) | enum Ordered implements ListStyleType {
      method Ordered (line 143) | Ordered(String name, STNumberFormat.Enum format, String text, String...
      method getName (line 150) | @Override
      method getFormat (line 155) | @Override
      method getText (line 160) | @Override
      method getFont (line 165) | @Override
      method of (line 170) | public static ListStyleType of(String type) {

FILE: src/main/java/org/ddr/poi/html/util/NamedBorderWidth.java
  type NamedBorderWidth (line 32) | public enum NamedBorderWidth {
    method NamedBorderWidth (line 43) | NamedBorderWidth(String name, int px) {
    method getName (line 48) | public String getName() {
    method getWidth (line 52) | public CSSLength getWidth() {
    method of (line 56) | public static NamedBorderWidth of(String name) {
    method contains (line 60) | public static boolean contains(String name) {

FILE: src/main/java/org/ddr/poi/html/util/NamedFontSize.java
  type NamedFontSize (line 32) | public enum NamedFontSize {
    method NamedFontSize (line 48) | NamedFontSize(String name, double pt) {
    method getName (line 53) | public String getName() {
    method getSize (line 57) | public CSSLength getSize() {
    method of (line 61) | public static NamedFontSize of(String name) {

FILE: src/main/java/org/ddr/poi/html/util/NumberingContext.java
  class NumberingContext (line 48) | public class NumberingContext {
    method NumberingContext (line 65) | public NumberingContext(XWPFDocument document) {
    method startLevel (line 74) | public void startLevel(ListStyle listStyle) {
    method endLevel (line 86) | public void endLevel() {
    method add (line 118) | public void add(XWPFParagraph paragraph) {
    method getNumberId (line 139) | private BigInteger getNumberId(String key) {
    method getLevelText (line 202) | private String getLevelText(ListStyleType listStyleType, int i) {
    method getOrderedLevelText (line 218) | private String getOrderedLevelText(int i) {
    method getFormatKey (line 222) | private String getFormatKey() {
    method setIndent (line 230) | public void setIndent(int indent) {
    method setHanging (line 236) | public void setHanging(int hanging) {
    method setSpacing (line 242) | public void setSpacing(STLevelSuffix.Enum spacing) {
    method contains (line 246) | public boolean contains(XWPFParagraph paragraph) {

FILE: src/main/java/org/ddr/poi/html/util/RenderUtils.java
  class RenderUtils (line 74) | public class RenderUtils {
    method align (line 133) | public static ParagraphAlignment align(String textAlign) {
    method underline (line 160) | public static STUnderline.Enum underline(String textDecorationStyle) {
    method getPPr (line 177) | public static CTPPr getPPr(CTStyle ctStyle) {
    method getPPr (line 181) | public static CTPPr getPPr(CTP ctp) {
    method getPBdr (line 185) | public static CTPBdr getPBdr(CTPPr pr) {
    method getJc (line 189) | public static CTJc getJc(CTPPr pr) {
    method getRPr (line 193) | public static CTRPr getRPr(CTR ctr) {
    method getTcPr (line 197) | public static CTTcPr getTcPr(CTTc tc) {
    method getTcMar (line 201) | public static CTTcMar getTcMar(CTTcPr tcPr) {
    method getTcMar (line 205) | public static CTTcMar getTcMar(XWPFTableCell cell) {
    method getShd (line 210) | public static CTShd getShd(CTPPr pPr) {
    method getInd (line 214) | public static CTInd getInd(CTPPr pPr) {
    method getInd (line 218) | public static CTInd getInd(XWPFParagraph paragraph) {
    method getSpacing (line 223) | public static CTSpacing getSpacing(CTPPr pPr) {
    method getSpacing (line 227) | public static CTSpacing getSpacing(XWPFParagraph paragraph) {
    method getColor (line 232) | public static CTColor getColor(CTRPr rPr) {
    method getUnderline (line 236) | public static CTUnderline getUnderline(CTRPr rPr) {
    method getAvailableWidthInEMU (line 246) | public static int getAvailableWidthInEMU(IBody body) {
    method paragraphStyle (line 288) | public static void paragraphStyle(HtmlRenderContext context, XWPFParag...
    method setSpacing (line 328) | private static void setSpacing(HtmlRenderContext context, XWPFParagrap...
    method setIndentation (line 374) | private static void setIndentation(HtmlRenderContext context, XWPFPara...
    method indent (line 423) | private static boolean indent(HtmlRenderContext context, XWPFParagraph...
    method setBorder (line 452) | private static boolean setBorder(HtmlRenderContext context, Object xwp...
    method borderStyle (line 488) | private static STBorder.Enum borderStyle(String style) {
    method getTblBorders (line 515) | public static CTTblBorders getTblBorders(CTTbl tbl) {
    method getTop (line 520) | private static CTBorder getTop(Object e) {
    method getParagraphTop (line 535) | public static CTBorder getParagraphTop(CTP paragraph) {
    method getTableTop (line 541) | public static CTBorder getTableTop(CTTbl table) {
    method getTableCellTop (line 546) | public static CTBorder getTableCellTop(CTTc cell) {
    method getRight (line 552) | private static CTBorder getRight(Object e) {
    method getParagraphRight (line 567) | public static CTBorder getParagraphRight(CTP paragraph) {
    method getTableRight (line 573) | public static CTBorder getTableRight(CTTbl tbl) {
    method getTableCellRight (line 578) | public static CTBorder getTableCellRight(CTTc cell) {
    method getBottom (line 584) | private static CTBorder getBottom(Object e) {
    method getParagraphBottom (line 599) | public static CTBorder getParagraphBottom(CTP paragraph) {
    method getTableBottom (line 605) | public static CTBorder getTableBottom(CTTbl table) {
    method getTableCellBottom (line 610) | public static CTBorder getTableCellBottom(CTTc cell) {
    method getLeft (line 616) | private static CTBorder getLeft(Object e) {
    method getParagraphLeft (line 631) | public static CTBorder getParagraphLeft(CTP paragraph) {
    method getTableLeft (line 637) | public static CTBorder getTableLeft(CTTbl table) {
    method getTableCellLeft (line 642) | public static CTBorder getTableCellLeft(CTTc cell) {
    method smallerFontSizeInHalfPoints (line 654) | public static int smallerFontSizeInHalfPoints(int inheritedSizeInHalfP...
    method largerFontSizeInHalfPoints (line 670) | public static int largerFontSizeInHalfPoints(int inheritedSizeInHalfPo...
    method emuToTwips (line 684) | public static int emuToTwips(int emu) {
    method tableStyle (line 695) | public static void tableStyle(HtmlRenderContext context, XWPFTable tab...
    method getTblInsideV (line 735) | public static CTBorder getTblInsideV(CTTblBorders tblBorders) {
    method getTblInsideH (line 739) | public static CTBorder getTblInsideH(CTTblBorders tblBorders) {
    method cellStyle (line 751) | public static void cellStyle(HtmlRenderContext context, XWPFTableCell ...
    method setCellPadding (line 787) | private static void setCellPadding(HtmlRenderContext context, XWPFTabl...
    method setBorder (line 829) | public static boolean setBorder(HtmlRenderContext context, Object xwpf...
    method indent (line 841) | private static boolean indent(HtmlRenderContext context, XWPFTable tab...
    method getInd (line 859) | public static CTTblWidth getInd(CTTblPr tblPr) {
    method getTblBorders (line 863) | public static CTTblBorders getTblBorders(CTTblPr tblPr) {
    method getShd (line 867) | public static CTShd getShd(CTTblPr tblPr) {
    method getTblPr (line 871) | public static CTTblPr getTblPr(CTTbl ctTbl) {
    method getTcBorders (line 879) | public static CTTcBorders getTcBorders(CTTcPr tcPr) {
    method getShd (line 883) | public static CTShd getShd(CTTcPr tcPr) {
    method alignTable (line 893) | public static TableRowAlign alignTable(String cssFloat) {
    method alignTableCell (line 913) | public static XWPFTableCell.XWPFVertAlign alignTableCell(String vertic...
    method inlineToAnchor (line 933) | public static CTAnchor inlineToAnchor(CTDrawing drawing) {

FILE: src/main/java/org/ddr/poi/html/util/Span.java
  class Span (line 27) | public class Span {
    method Span (line 33) | public Span(int row, int column, boolean enabled, CSSStyleDeclarationI...
    method getRow (line 40) | public int getRow() {
    method getColumn (line 44) | public int getColumn() {
    method isEnabled (line 48) | public boolean isEnabled() {
    method setRow (line 52) | public void setRow(int row) {
    method setColumn (line 56) | public void setColumn(int column) {
    method setEnabled (line 60) | public void setEnabled(boolean enabled) {
    method getStyle (line 64) | public CSSStyleDeclarationImpl getStyle() {
    method toString (line 68) | public String toString() {

FILE: src/main/java/org/ddr/poi/html/util/SpanWidth.java
  class SpanWidth (line 28) | public class SpanWidth extends CSSLength {
    method SpanWidth (line 34) | public SpanWidth(CSSLength length, int column, int span, boolean expli...
    method setLength (line 42) | public void setLength(Map<Integer, CSSLength> map) {
    method getSpan (line 87) | public int getSpan() {
    method getColumn (line 91) | public int getColumn() {
    method getLengths (line 95) | public CSSLength[] getLengths() {
    method equals (line 99) | @Override
    method hashCode (line 110) | @Override

FILE: src/main/java/org/ddr/poi/html/util/WhiteSpaceRule.java
  type WhiteSpaceRule (line 32) | public enum WhiteSpaceRule {
    method WhiteSpaceRule (line 45) | WhiteSpaceRule(String value, boolean keepLineBreak, boolean keepSpaceA...
    method getValue (line 52) | public String getValue() {
    method isKeepLineBreak (line 56) | public boolean isKeepLineBreak() {
    method isKeepSpaceAndTab (line 60) | public boolean isKeepSpaceAndTab() {
    method isKeepTrailingSpace (line 64) | public boolean isKeepTrailingSpace() {
    method isNormal (line 68) | public boolean isNormal() {
    method of (line 76) | public static WhiteSpaceRule of(String value) {
    method of (line 80) | public static WhiteSpaceRule of(String value, WhiteSpaceRule defaultRu...

FILE: src/main/java/org/ddr/poi/html/util/XWPFParagraphRuns.java
  class XWPFParagraphRuns (line 16) | public class XWPFParagraphRuns {
    method XWPFParagraphRuns (line 33) | @SuppressWarnings("unchecked")
    method remove (line 48) | public void remove(int pos) {
    method runCount (line 56) | public int runCount() {

FILE: src/main/java/org/ddr/poi/latex/LaTeXRenderPolicy.java
  class LaTeXRenderPolicy (line 17) | public class LaTeXRenderPolicy extends AbstractRenderPolicy<String> {
    method LaTeXRenderPolicy (line 21) | public LaTeXRenderPolicy() {
    method LaTeXRenderPolicy (line 25) | public LaTeXRenderPolicy(MathRenderConfig config) {
    method getConfig (line 29) | public MathRenderConfig getConfig() {
    method validate (line 33) | @Override
    method doRender (line 44) | @Override
    method afterRender (line 51) | @Override

FILE: src/main/java/org/ddr/poi/latex/LaTeXUtils.java
  class LaTeXUtils (line 46) | public class LaTeXUtils {
    method createSession (line 57) | public static SnuggleSession createSession() {
    method parse (line 68) | public static boolean parse(SnuggleSession session, String data) {
    method renderTo (line 93) | public static void renderTo(XWPFParagraph paragraph, CTR ctr, SnuggleS...
    method renderTag (line 113) | private static void renderTag(XWPFParagraph paragraph, CTR ctr, Node n...
    class Initializer (line 168) | private static class Initializer {

FILE: src/main/java/org/ddr/poi/latex/TagHandler.java
  class TagHandler (line 18) | public class TagHandler implements CommandHandler {
    method handleCommand (line 19) | @Override

FILE: src/main/java/org/ddr/poi/latex/TextCircledHandler.java
  class TextCircledHandler (line 11) | public class TextCircledHandler implements CommandHandler {
    method handleCommand (line 15) | @Override

FILE: src/main/java/org/ddr/poi/math/EmptyEOfNaryDisplayMode.java
  type EmptyEOfNaryDisplayMode (line 9) | public enum EmptyEOfNaryDisplayMode {
    method EmptyEOfNaryDisplayMode (line 29) | EmptyEOfNaryDisplayMode(int value) {
    method getValue (line 33) | public int getValue() {
    method isZeroWidth (line 37) | public boolean isZeroWidth() {
    method isHidden (line 41) | public boolean isHidden() {

FILE: src/main/java/org/ddr/poi/math/MathMLRenderPolicy.java
  class MathMLRenderPolicy (line 32) | public class MathMLRenderPolicy extends AbstractRenderPolicy<String> {
    method MathMLRenderPolicy (line 36) | public MathMLRenderPolicy() {
    method MathMLRenderPolicy (line 40) | public MathMLRenderPolicy(MathRenderConfig config) {
    method getConfig (line 44) | public MathRenderConfig getConfig() {
    method validate (line 48) | @Override
    method doRender (line 57) | @Override
    method afterRender (line 66) | @Override

FILE: src/main/java/org/ddr/poi/math/MathMLUtils.java
  class MathMLUtils (line 55) | public class MathMLUtils {
    method renderTo (line 79) | public static void renderTo(XWPFParagraph paragraph, CTR ctr, String m...
    method normalize (line 102) | public static String normalize(String math) {
    class Initializer (line 121) | private static class Initializer {
      method createTransformer (line 125) | private static Xslt30Transformer createTransformer() {
    method newSerializer (line 137) | private static Serializer newSerializer(StringWriter sw) {
    method addMath (line 152) | private static void addMath(XWPFParagraph paragraph, CTR ctr, String o...
    method insertMathAfterRun (line 202) | private static void insertMathAfterRun(CTP ctp, CTR ctr, CTOMath ctoMa...

FILE: src/main/java/org/ddr/poi/math/MathRenderConfig.java
  class MathRenderConfig (line 8) | public class MathRenderConfig {
    method getEmptyEOfNaryOption (line 11) | public EmptyEOfNaryDisplayMode getEmptyEOfNaryOption() {
    method setEmptyEOfNaryOption (line 15) | public void setEmptyEOfNaryOption(EmptyEOfNaryDisplayMode emptyEOfNary...

FILE: src/main/java/org/ddr/poi/util/ByteArrayCopyStream.java
  class ByteArrayCopyStream (line 28) | public class ByteArrayCopyStream extends ByteArrayOutputStream {
    method ByteArrayCopyStream (line 29) | public ByteArrayCopyStream() {
    method ByteArrayCopyStream (line 32) | public ByteArrayCopyStream(int size) {
    method toInput (line 39) | public ByteArrayInputStream toInput() {

FILE: src/main/java/org/ddr/poi/util/HttpURLConnectionUtils.java
  class HttpURLConnectionUtils (line 49) | public class HttpURLConnectionUtils {
    class X509TrustAllManager (line 53) | public static class X509TrustAllManager implements X509TrustManager {
      method checkClientTrusted (line 54) | @Override
      method checkServerTrusted (line 58) | @Override
      method getAcceptedIssuers (line 62) | @Override
    class TrustAllHostname (line 68) | public static class TrustAllHostname implements HostnameVerifier {
      method verify (line 69) | @Override
    method connect (line 81) | public static HttpURLConnection connect(String urlSpec) throws IOExcep...
    method connect (line 93) | public static HttpURLConnection connect(String urlSpec, String user, S...
    method trustAllSslSocketFactory (line 121) | public static SSLSocketFactory trustAllSslSocketFactory() {
    method initFormData (line 138) | public static String initFormData(HttpURLConnection connect) throws Pr...
    method initUserAgent (line 149) | public static void initUserAgent(HttpURLConnection connect) {
    method addFormData (line 159) | public static void addFormData(OutputStream outputStream, byte[] bound...

FILE: src/main/java/org/ddr/poi/util/XmlUtils.java
  class XmlUtils (line 30) | public class XmlUtils {
    method removeNamespaces (line 55) | public static void removeNamespaces(XmlObject xmlObject) {

FILE: src/main/java/org/jsoup/parser/CustomHtmlTreeBuilder.java
  class CustomHtmlTreeBuilder (line 15) | public class CustomHtmlTreeBuilder extends HtmlTreeBuilder {
    method reconstructFormattingElements (line 16) | @Override
    method insert (line 30) | @Override
    method parseFragment (line 45) | @Override
    method isSvgElement (line 50) | private boolean isSvgElement() {

FILE: src/test/java/org/ddr/poi/FileReader.java
  class FileReader (line 29) | public class FileReader {
    method readFile (line 30) | public static String readFile(String resourceFile) throws IOException {

FILE: src/test/java/org/ddr/poi/html/HtmlRenderPolicyTest.java
  class HtmlRenderPolicyTest (line 29) | class HtmlRenderPolicyTest {
    method doRender (line 31) | @Test

FILE: src/test/java/org/ddr/poi/latex/LaTeXRenderPolicyTest.java
  class LaTeXRenderPolicyTest (line 13) | class LaTeXRenderPolicyTest {
    method doRender (line 15) | @Test

FILE: src/test/java/org/ddr/poi/math/MathMLRenderPolicyTest.java
  class MathMLRenderPolicyTest (line 36) | class MathMLRenderPolicyTest {
    method doRender (line 38) | @Test
Condensed preview — 114 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (678K chars).
[
  {
    "path": ".gitattributes",
    "chars": 154,
    "preview": "#\n# https://help.github.com/articles/dealing-with-line-endings/\n#\n# These are explicitly windows files and should use cr"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "chars": 552,
    "preview": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Describe the b"
  },
  {
    "path": ".gitignore",
    "chars": 673,
    "preview": "# Compiled class file\n*.class\n\n# Log file\n*.log\n\n# BlueJ files\n*.ctxt\n\n# Mobile Tools for Java (J2ME)\n.mtj.tmp/\n\n# Packa"
  },
  {
    "path": "LICENSE",
    "chars": 11357,
    "preview": "                                 Apache License\n                           Version 2.0, January 2004\n                   "
  },
  {
    "path": "README.md",
    "chars": 1945,
    "preview": "# poi-tl-ext\n![GitHub](https://img.shields.io/github/license/draco1023/poi-tl-ext) ![JDK](https://img.shields.io/badge/j"
  },
  {
    "path": "build.gradle",
    "chars": 3704,
    "preview": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (th"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "chars": 812,
    "preview": "#\n# Copyright 2016 - 2021 Draco, https://github.com/draco1023\n#\n# Licensed under the Apache License, Version 2.0 (the \"L"
  },
  {
    "path": "gradlew",
    "chars": 5794,
    "preview": "#!/usr/bin/env sh\n\n#\n# Copyright 2015 the original author or authors.\n#\n# Licensed under the Apache License, Version 2.0"
  },
  {
    "path": "gradlew.bat",
    "chars": 3082,
    "preview": "@rem\r\n@rem Copyright 2015 the original author or authors.\r\n@rem\r\n@rem Licensed under the Apache License, Version 2.0 (th"
  },
  {
    "path": "settings.gradle",
    "chars": 659,
    "preview": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (th"
  },
  {
    "path": "src/main/java/org/apache/poi/xwpf/usermodel/SVGPictureData.java",
    "chars": 482,
    "preview": "package org.apache.poi.xwpf.usermodel;\n\nimport org.apache.poi.openxml4j.opc.PackagePart;\n\n/**\n * SVGPictureData\n *\n * @a"
  },
  {
    "path": "src/main/java/org/apache/poi/xwpf/usermodel/SVGRelation.java",
    "chars": 1479,
    "preview": "package org.apache.poi.xwpf.usermodel;\n\nimport org.apache.poi.ooxml.POIXMLRelation;\nimport org.apache.poi.openxml4j.opc."
  },
  {
    "path": "src/main/java/org/ddr/image/ImageInfo.java",
    "chars": 1231,
    "preview": "package org.ddr.image;\n\nimport java.awt.*;\nimport java.io.ByteArrayInputStream;\n\npublic class ImageInfo {\n    private By"
  },
  {
    "path": "src/main/java/org/ddr/image/ImageInputStreamWrapper.java",
    "chars": 4935,
    "preview": "package org.ddr.image;\n\nimport javax.imageio.stream.IIOByteBuffer;\nimport javax.imageio.stream.ImageInputStream;\nimport "
  },
  {
    "path": "src/main/java/org/ddr/image/ImageType.java",
    "chars": 796,
    "preview": "package org.ddr.image;\n\nimport org.apache.poi.xwpf.usermodel.Document;\n\npublic enum ImageType {\n    EMF(Document.PICTURE"
  },
  {
    "path": "src/main/java/org/ddr/image/MetadataReader.java",
    "chars": 277,
    "preview": "package org.ddr.image;\n\nimport com.drew.imaging.FileType;\nimport com.drew.metadata.Metadata;\n\nimport java.awt.*;\n\npublic"
  },
  {
    "path": "src/main/java/org/ddr/image/MetadataReaders.java",
    "chars": 863,
    "preview": "package org.ddr.image;\n\nimport org.ddr.image.avif.AvifMetadataReader;\nimport org.ddr.image.bmp.BmpMetadataReader;\nimport"
  },
  {
    "path": "src/main/java/org/ddr/image/avif/AvifImageReader.java",
    "chars": 292,
    "preview": "package org.ddr.image.avif;\n\nimport org.ddr.image.heif.HeifImageReader;\n\nimport javax.imageio.spi.ImageReaderSpi;\n\npubli"
  },
  {
    "path": "src/main/java/org/ddr/image/avif/AvifImageReaderSpi.java",
    "chars": 1829,
    "preview": "package org.ddr.image.avif;\n\nimport com.twelvemonkeys.imageio.spi.ImageReaderSpiBase;\n\nimport javax.imageio.ImageReader;"
  },
  {
    "path": "src/main/java/org/ddr/image/avif/AvifMetadataReader.java",
    "chars": 376,
    "preview": "package org.ddr.image.avif;\n\nimport com.drew.imaging.FileType;\nimport org.ddr.image.heif.HeifMetadataReader;\n\npublic cla"
  },
  {
    "path": "src/main/java/org/ddr/image/avif/AvifProviderInfo.java",
    "chars": 725,
    "preview": "package org.ddr.image.avif;\n\nimport com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;\n\nfinal class AvifProviderInf"
  },
  {
    "path": "src/main/java/org/ddr/image/bmp/BmpMetadataReader.java",
    "chars": 1091,
    "preview": "package org.ddr.image.bmp;\n\nimport com.drew.imaging.FileType;\nimport com.drew.metadata.Directory;\nimport com.drew.metada"
  },
  {
    "path": "src/main/java/org/ddr/image/eps/EpsMetadataReader.java",
    "chars": 1066,
    "preview": "package org.ddr.image.eps;\n\nimport com.drew.imaging.FileType;\nimport com.drew.metadata.Directory;\nimport com.drew.metada"
  },
  {
    "path": "src/main/java/org/ddr/image/gif/GifMetadataReader.java",
    "chars": 1090,
    "preview": "package org.ddr.image.gif;\n\nimport com.drew.imaging.FileType;\nimport com.drew.metadata.Directory;\nimport com.drew.metada"
  },
  {
    "path": "src/main/java/org/ddr/image/heif/HeifImageReader.java",
    "chars": 7853,
    "preview": "package org.ddr.image.heif;\n\nimport com.drew.imaging.FileType;\nimport com.drew.imaging.ImageMetadataReader;\nimport com.d"
  },
  {
    "path": "src/main/java/org/ddr/image/heif/HeifImageReaderSpi.java",
    "chars": 2179,
    "preview": "package org.ddr.image.heif;\n\nimport com.twelvemonkeys.imageio.spi.ImageReaderSpiBase;\n\nimport javax.imageio.ImageReader;"
  },
  {
    "path": "src/main/java/org/ddr/image/heif/HeifMetadataReader.java",
    "chars": 2735,
    "preview": "package org.ddr.image.heif;\n\nimport com.drew.imaging.FileType;\nimport com.drew.metadata.Directory;\nimport com.drew.metad"
  },
  {
    "path": "src/main/java/org/ddr/image/heif/HeifProviderInfo.java",
    "chars": 769,
    "preview": "package org.ddr.image.heif;\n\nimport com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;\n\nfinal class HeifProviderInf"
  },
  {
    "path": "src/main/java/org/ddr/image/jpeg/JpegMetadataReader.java",
    "chars": 1075,
    "preview": "package org.ddr.image.jpeg;\n\nimport com.drew.imaging.FileType;\nimport com.drew.metadata.Directory;\nimport com.drew.metad"
  },
  {
    "path": "src/main/java/org/ddr/image/png/PngMetadataReader.java",
    "chars": 1236,
    "preview": "package org.ddr.image.png;\n\nimport com.drew.imaging.FileType;\nimport com.drew.imaging.png.PngChunkType;\nimport com.drew."
  },
  {
    "path": "src/main/java/org/ddr/image/tiff/TiffMetadataReader.java",
    "chars": 1276,
    "preview": "package org.ddr.image.tiff;\n\nimport com.drew.imaging.FileType;\nimport com.drew.metadata.Directory;\nimport com.drew.metad"
  },
  {
    "path": "src/main/java/org/ddr/image/webp/WebpMetadataReader.java",
    "chars": 1421,
    "preview": "package org.ddr.image.webp;\n\nimport com.drew.imaging.FileType;\nimport com.drew.metadata.Directory;\nimport com.drew.metad"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/ElementRenderer.java",
    "chars": 1323,
    "preview": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (th"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/ElementRendererProvider.java",
    "chars": 944,
    "preview": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (th"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/HtmlConstants.java",
    "chars": 11830,
    "preview": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (th"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/HtmlRenderConfig.java",
    "chars": 3562,
    "preview": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (th"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/HtmlRenderContext.java",
    "chars": 58360,
    "preview": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (th"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/HtmlRenderPolicy.java",
    "chars": 7679,
    "preview": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (th"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/tag/ARenderer.java",
    "chars": 1712,
    "preview": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (th"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/tag/BigRenderer.java",
    "chars": 1792,
    "preview": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (th"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/tag/BoldRenderer.java",
    "chars": 1790,
    "preview": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (th"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/tag/BreakRenderer.java",
    "chars": 1796,
    "preview": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (th"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/tag/DeleteRenderer.java",
    "chars": 1818,
    "preview": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (th"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/tag/FigureCaptionRenderer.java",
    "chars": 1186,
    "preview": "package org.ddr.poi.html.tag;\n\nimport org.ddr.poi.html.ElementRenderer;\nimport org.ddr.poi.html.HtmlConstants;\nimport or"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/tag/FigureRenderer.java",
    "chars": 2426,
    "preview": "package org.ddr.poi.html.tag;\n\nimport com.steadystate.css.dom.CSSStyleDeclarationImpl;\nimport org.apache.poi.xwpf.usermo"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/tag/HeaderBreakRenderer.java",
    "chars": 2492,
    "preview": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (th"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/tag/HeaderRenderer.java",
    "chars": 2721,
    "preview": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (th"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/tag/ImageRenderer.java",
    "chars": 15720,
    "preview": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (th"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/tag/ItalicRenderer.java",
    "chars": 1790,
    "preview": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (th"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/tag/LaTeXRenderer.java",
    "chars": 1867,
    "preview": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (th"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/tag/ListItemRenderer.java",
    "chars": 1830,
    "preview": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (th"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/tag/ListRenderer.java",
    "chars": 3755,
    "preview": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (th"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/tag/MarkRenderer.java",
    "chars": 1730,
    "preview": "/*\n * Copyright 2016 - 2022 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (th"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/tag/MathRenderer.java",
    "chars": 1911,
    "preview": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (th"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/tag/OmittedRenderer.java",
    "chars": 1802,
    "preview": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (th"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/tag/PreRenderer.java",
    "chars": 942,
    "preview": "package org.ddr.poi.html.tag;\n\nimport org.ddr.poi.html.ElementRenderer;\nimport org.ddr.poi.html.HtmlConstants;\nimport or"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/tag/RubyRenderer.java",
    "chars": 4015,
    "preview": "/*\n * Copyright 2016 - 2022 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (th"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/tag/SmallRenderer.java",
    "chars": 1775,
    "preview": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (th"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/tag/SubscriptRenderer.java",
    "chars": 1777,
    "preview": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (th"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/tag/SuperscriptRenderer.java",
    "chars": 1781,
    "preview": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (th"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/tag/SvgRenderer.java",
    "chars": 2315,
    "preview": "package org.ddr.poi.html.tag;\n\nimport org.apache.poi.openxml4j.exceptions.InvalidFormatException;\nimport org.ddr.image.I"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/tag/TableCellRenderer.java",
    "chars": 3269,
    "preview": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (th"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/tag/TableRenderer.java",
    "chars": 29360,
    "preview": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (th"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/tag/UnderlineRenderer.java",
    "chars": 1773,
    "preview": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (th"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/tag/WalkThroughRenderer.java",
    "chars": 1621,
    "preview": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (th"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/util/BoxProperty.java",
    "chars": 3244,
    "preview": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (th"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/util/CSSLength.java",
    "chars": 3707,
    "preview": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (th"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/util/CSSLengthUnit.java",
    "chars": 4076,
    "preview": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (th"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/util/CSSStyleUtils.java",
    "chars": 16562,
    "preview": "package org.ddr.poi.html.util;\n\nimport com.steadystate.css.dom.CSSStyleDeclarationImpl;\nimport com.steadystate.css.dom.C"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/util/Colors.java",
    "chars": 15578,
    "preview": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (th"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/util/ColumnStyle.java",
    "chars": 526,
    "preview": "package org.ddr.poi.html.util;\n\nimport com.steadystate.css.dom.CSSStyleDeclarationImpl;\n\n/**\n * 表格列样式定义\n *\n * @author Dr"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/util/EmptyCSSStyle.java",
    "chars": 910,
    "preview": "package org.ddr.poi.html.util;\n\nimport com.steadystate.css.dom.CSSStyleDeclarationImpl;\nimport com.steadystate.css.dom.P"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/util/InlineStyle.java",
    "chars": 1269,
    "preview": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (th"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/util/JsoupUtils.java",
    "chars": 3933,
    "preview": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (th"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/util/ListStyle.java",
    "chars": 1003,
    "preview": "package org.ddr.poi.html.util;\n\n/**\n * 列表样式\n *\n * @author Draco\n * @since 2024-09-12\n */\npublic class ListStyle {\n    pr"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/util/ListStyleType.java",
    "chars": 6698,
    "preview": "package org.ddr.poi.html.util;\n\nimport org.openxmlformats.schemas.wordprocessingml.x2006.main.STNumberFormat;\n\nimport ja"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/util/NamedBorderWidth.java",
    "chars": 1752,
    "preview": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (th"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/util/NamedFontSize.java",
    "chars": 1840,
    "preview": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (th"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/util/NumberingContext.java",
    "chars": 8793,
    "preview": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (th"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/util/RenderUtils.java",
    "chars": 36120,
    "preview": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (th"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/util/Span.java",
    "chars": 1780,
    "preview": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (th"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/util/SpanWidth.java",
    "chars": 3595,
    "preview": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (th"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/util/WhiteSpaceRule.java",
    "chars": 2545,
    "preview": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (th"
  },
  {
    "path": "src/main/java/org/ddr/poi/html/util/XWPFParagraphRuns.java",
    "chars": 1475,
    "preview": "package org.ddr.poi.html.util;\n\nimport org.apache.poi.xwpf.usermodel.IRunElement;\nimport org.apache.poi.xwpf.usermodel.X"
  },
  {
    "path": "src/main/java/org/ddr/poi/latex/LaTeXRenderPolicy.java",
    "chars": 1576,
    "preview": "package org.ddr.poi.latex;\n\nimport com.deepoove.poi.policy.AbstractRenderPolicy;\nimport com.deepoove.poi.render.RenderCo"
  },
  {
    "path": "src/main/java/org/ddr/poi/latex/LaTeXUtils.java",
    "chars": 8306,
    "preview": "package org.ddr.poi.latex;\n\nimport org.apache.commons.collections4.CollectionUtils;\nimport org.apache.poi.xwpf.usermodel"
  },
  {
    "path": "src/main/java/org/ddr/poi/latex/TagHandler.java",
    "chars": 2375,
    "preview": "package org.ddr.poi.latex;\n\nimport org.w3c.dom.Document;\nimport org.w3c.dom.Element;\nimport org.w3c.dom.Node;\nimport uk."
  },
  {
    "path": "src/main/java/org/ddr/poi/latex/TextCircledHandler.java",
    "chars": 1153,
    "preview": "package org.ddr.poi.latex;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.w3c.dom.Element;\nimport "
  },
  {
    "path": "src/main/java/org/ddr/poi/math/EmptyEOfNaryDisplayMode.java",
    "chars": 688,
    "preview": "package org.ddr.poi.math;\n\n/**\n * 空的N元组显示模式\n *\n * @author Draco\n * @since 2025-06-09 16:44\n */\npublic enum EmptyEOfNaryD"
  },
  {
    "path": "src/main/java/org/ddr/poi/math/MathMLRenderPolicy.java",
    "chars": 2107,
    "preview": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (th"
  },
  {
    "path": "src/main/java/org/ddr/poi/math/MathMLUtils.java",
    "chars": 9168,
    "preview": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (th"
  },
  {
    "path": "src/main/java/org/ddr/poi/math/MathRenderConfig.java",
    "chars": 486,
    "preview": "package org.ddr.poi.math;\n\n/**\n * 公式渲染配置\n * @author Draco\n * @since 2025-06-09 16:08\n */\npublic class MathRenderConfig {"
  },
  {
    "path": "src/main/java/org/ddr/poi/util/ByteArrayCopyStream.java",
    "chars": 1127,
    "preview": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (th"
  },
  {
    "path": "src/main/java/org/ddr/poi/util/HttpURLConnectionUtils.java",
    "chars": 6588,
    "preview": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (th"
  },
  {
    "path": "src/main/java/org/ddr/poi/util/XmlUtils.java",
    "chars": 2393,
    "preview": "/*\n * Copyright 2016 - 2022 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (th"
  },
  {
    "path": "src/main/java/org/jsoup/parser/CustomHtmlTreeBuilder.java",
    "chars": 1570,
    "preview": "package org.jsoup.parser;\n\nimport org.ddr.poi.html.HtmlConstants;\nimport org.jsoup.nodes.Element;\nimport org.jsoup.nodes"
  },
  {
    "path": "src/main/resources/META-INF/services/javax.imageio.spi.ImageReaderSpi",
    "chars": 75,
    "preview": "org.ddr.image.avif.AvifImageReaderSpi\norg.ddr.image.heif.HeifImageReaderSpi"
  },
  {
    "path": "src/main/resources/MML2OMML.XSL",
    "chars": 180809,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<xsl:stylesheet version=\"1.0\" xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\"\n "
  },
  {
    "path": "src/main/resources/math-character-aliases.txt",
    "chars": 55,
    "preview": "to->rightarrow\ngets->leftarrow\nimplies->Longrightarrow\n"
  },
  {
    "path": "src/main/resources/math-character-circled.txt",
    "chars": 605,
    "preview": "0->⓪\n1->①\n2->②\n3->③\n4->④\n5->⑤\n6->⑥\n7->⑦\n8->⑧\n9->⑨\n10->⑩\n11->⑪\n12->⑫\n13->⑬\n14->⑭\n15->⑮\n16->⑯\n17->⑰\n18->⑱\n19->⑲\n20->⑳\n21->"
  },
  {
    "path": "src/test/java/org/ddr/poi/FileReader.java",
    "chars": 1126,
    "preview": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (th"
  },
  {
    "path": "src/test/java/org/ddr/poi/html/HtmlRenderPolicyTest.java",
    "chars": 1886,
    "preview": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (th"
  },
  {
    "path": "src/test/java/org/ddr/poi/latex/LaTeXRenderPolicyTest.java",
    "chars": 1668,
    "preview": "package org.ddr.poi.latex;\n\nimport com.deepoove.poi.XWPFTemplate;\nimport com.deepoove.poi.config.Configure;\nimport org.d"
  },
  {
    "path": "src/test/java/org/ddr/poi/math/MathMLRenderPolicyTest.java",
    "chars": 2094,
    "preview": "/*\n * Copyright 2016 - 2021 Draco, https://github.com/draco1023\n *\n * Licensed under the Apache License, Version 2.0 (th"
  },
  {
    "path": "src/test/resources/0.xml",
    "chars": 2268,
    "preview": "<math>\n    <mtable columnalign=\"right center left\">\n        <mtr>\n            <mtd>\n                <msup>\n             "
  },
  {
    "path": "src/test/resources/1.html",
    "chars": 1151,
    "preview": "<p style=\"font: italic small-caps bold 16px/2 cursive, sans-serif;\">\n    <span style=\"font-size: 36px\">你好<strong>世界<i> 你"
  },
  {
    "path": "src/test/resources/1.xml",
    "chars": 446,
    "preview": "<math xmlns=\"http://www.w3.org/1998/Math/MathML\">\n    <mrow>\n        <mrow>\n            <msup>\n                <mi>x</mi"
  },
  {
    "path": "src/test/resources/2.html",
    "chars": 40948,
    "preview": "<p><span style=\"font-size: 36px\">你好<strong>世界<i> 你敢信</i></strong>啊!</span></p><p><span\n        style=\"font-size: 24px\">你"
  },
  {
    "path": "src/test/resources/2.xml",
    "chars": 101,
    "preview": "<math xmlns=\"http://www.w3.org/1998/Math/MathML\">\n    <msqrt>\n        <mn>4</mn>\n    </msqrt>\n</math>"
  },
  {
    "path": "src/test/resources/3.xml",
    "chars": 197,
    "preview": "<math xmlns=\"http://www.w3.org/1998/Math/MathML\">\n    <mi>&#x03C0;<!-- π --></mi>\n    <mo>&#x2062;<!-- &InvisibleTimes; "
  },
  {
    "path": "src/test/resources/simplelogger.properties",
    "chars": 737,
    "preview": "#\n# Copyright 2016 - 2021 Draco, https://github.com/draco1023\n#\n# Licensed under the Apache License, Version 2.0 (the \"L"
  }
]

// ... and 3 more files (download for full content)

About this extraction

This page contains the full source code of the draco1023/poi-tl-ext GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 114 files (616.6 KB), approximately 157.4k tokens, and a symbol index with 681 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!