diff --git a/.github/GitHub-Actions.org b/.github/GitHub-Actions.org
new file mode 100644
index 0000000..1c882b8
--- /dev/null
+++ b/.github/GitHub-Actions.org
@@ -0,0 +1,41 @@
+* Deploying using Github Actions
+
+** Actions definition: workflow/sonatype-deploy.yml
+
+When a GitHub Release is created, usually from a tag, this action will trigger.
+
+Using JDK8 the software will be packaged, including running any tests.
+
+Then the Deploy script will sign the created artifacts then deploy them according to the distributionManagement configuration in the `pom.xml`.
+
+** Deploy Script
+
+Uses a signing key provided from the GitHub Actions Secrets as an environment variable to sign the artifact(s) before they are then deployed.
+
+*** Inputs
+
+**** DEPLOY_PROJECTS (optional)
+
+An optional list of modules in a multi-module project to be deployed. If this value is not specified, then all projects will be deployed.
+
+** Maven Configuration
+
+Picks up the credentials from Environment variables for authenticating both with GPG and with the target deployment server (e.g. sonatype-nexus).
+
+*** Inputs
+
+**** NEXUS_USERNAME
+
+The username for your account on the deployment server.
+
+**** NEXUS_PASSWORD
+
+The password for your account on the deployement server.
+
+**** GPG_KEYNAME
+
+The key to use when signing.
+
+**** GPG_PASSPHRASE
+
+The passphrase to unlock the key to use when signing.
diff --git a/.github/NOTES b/.github/NOTES
new file mode 100644
index 0000000..64253c9
--- /dev/null
+++ b/.github/NOTES
@@ -0,0 +1,53 @@
+Add subkeys:
+
+????
+
+Publish:
+
+gpg --send-keys --keyserver keyserver.ubuntu.com $KEYID
+gpg --send-keys --keyserver pgp.mit.edu $KEYID
+gpg --send-keys --keyserver pool.sks-keyservers.net $KEYID
+
+Backup:
+
+gpg --export --armor pcampbell@kemitix.net > gpg-key-backup.asc
+gpg --export-secret-keys --armor pcampbell@kemitix.net >> gpg-key-backup.asc
+
+Export sub-keys:
+
+gpg --export-secret-subkeys pcampbell@kemitix.net > subkeys
+
+Remove master keys:
+
+gpg --delete-secret-key pcampbell@kemitix.net
+
+Import sub-keys and clean up:
+
+gpg --import subkeys
+
+shred --remove subkeys
+
+Delete any encryption subkeys:
+
+gpg --edit-key pcampbell@kemitix.net
+
+2
+delkey
+save
+
+Change passphrase:
+
+gpg --edit-key pcampbell@kemitix.net
+passwd
+save
+
+Export keys:
+
+gpg --export --armor pcampbell@kemitix.net > codesigning.asc
+gpg --export-secret-keys --armor pcampbell@kemitix.net >> codesigning.asc
+
+Encrypt keys:
+
+gpg --symmetric --cipher-algo AES256 codesigning.asc
+
+shred codesigning.asc
diff --git a/.github/codesigning.asc.gpg b/.github/codesigning.asc.gpg
new file mode 100644
index 0000000..f5c71e0
Binary files /dev/null and b/.github/codesigning.asc.gpg differ
diff --git a/.github/settings.xml b/.github/settings.xml
new file mode 100644
index 0000000..8791e47
--- /dev/null
+++ b/.github/settings.xml
@@ -0,0 +1,28 @@
+
+
+
+
+ sonatype-nexus-snapshots
+ ${env.NEXUS_USERNAME}
+ ${env.NEXUS_PASSWORD}
+
+
+ sonatype-nexus-staging
+ ${env.NEXUS_USERNAME}
+ ${env.NEXUS_PASSWORD}
+
+
+
+
+ gpg-sign
+
+ true
+
+
+ gpg
+ ${env.GPG_KEYNAME}
+ ${env.GPG_PASSPHRASE}
+
+
+
+
diff --git a/.github/workflows/maven-build.yml b/.github/workflows/maven-build.yml
new file mode 100644
index 0000000..dc00080
--- /dev/null
+++ b/.github/workflows/maven-build.yml
@@ -0,0 +1,26 @@
+# This workflow will build a Java project with Maven
+# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven
+
+name: Java CI with Maven
+
+on:
+ push:
+ branches: '*'
+ pull_request:
+ branches: '*'
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ java: [ 11, 13 ]
+ steps:
+ - uses: kamiazya/setup-graphviz@v1
+ - uses: actions/checkout@v2
+ - name: setup-java-${{ matrix.java }}
+ uses: actions/setup-java@v1
+ with:
+ java-version: ${{ matrix.java }}
+ - name: install
+ run: mvn -B install
diff --git a/.github/workflows/sonatype-deploy.yml b/.github/workflows/sonatype-deploy.yml
new file mode 100644
index 0000000..2465173
--- /dev/null
+++ b/.github/workflows/sonatype-deploy.yml
@@ -0,0 +1,39 @@
+name: Deploy to Sonatype Nexus
+
+on:
+ release:
+ types: [created]
+
+jobs:
+ deploy:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: kamiazya/setup-graphviz@v1
+ - uses: actions/checkout@v2
+ - name: Set up JDK 1.8
+ uses: actions/setup-java@v1
+ with:
+ java-version: 1.8
+ - name: Build with Maven
+ run: mvn -B install
+ - name: Nexus Repo Publish
+ run: |
+ gpg --quiet \
+ --batch \
+ --yes \
+ --decrypt \
+ --passphrase="${{ secrets.GPG_PASSPHRASE }}" \
+ --output codesigning.asc \
+ .github/codesigning.asc.gpg
+ gpg --batch \
+ --fast-import codesigning.asc
+ mvn --settings .github/settings.xml \
+ -Dskip-Tests=true \
+ -P release \
+ -B \
+ deploy
+ env:
+ NEXUS_USERNAME: ${{ secrets.NEXUS_USERNAME }}
+ NEXUS_PASSWORD: ${{ secrets.NEXUS_PASSWORD }}
+ GPG_KEYNAME: ${{ secrets.GPG_KEYNAME }}
+ GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
diff --git a/.gitignore b/.gitignore
index a1c2a23..2ea9f5e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -21,3 +21,17 @@
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
+
+*.jqwik-database*
+
+*.iml
+.idea/
+target/
+pom.xml.*
+release.properties
+dependency-reduced-pom.xml
+buildNumber.properties
+.mvn/timing.properties
+
+# Avoid ignoring Maven wrapper jar file (.jar files are usually ignored)
+!/.mvn/wrapper/maven-wrapper.jar
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..08e9fcb
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,85 @@
+
+
+ 4.0.0
+
+ net.kemitix
+ kemitix-parent
+ 5.3.0
+
+
+
+ text-wrap-fit-box
+ DEV-SNAPSHOT
+
+
+ 2.16
+ 2.6.0
+ 5.4.0
+ 1.18.12
+
+ 5.6.2
+ 3.16.0
+ 3.3.3
+ 1.2.7
+
+
+
+
+ org.projectlombok
+ lombok
+ ${lombok.version}
+
+
+
+ org.junit.jupiter
+ junit-jupiter
+ ${junit.version}
+ test
+
+
+
+
+
+
+
+
+ org.assertj
+ assertj-core
+ ${assertj.version}
+ test
+
+
+
+
+
+
+
+
+
+
+
+
+ io.repaint.maven
+ tiles-maven-plugin
+ ${tiles-maven-plugin.version}
+ true
+
+
+ net.kemitix.tiles:maven-plugins:${kemitix-maven-tiles.version}
+ net.kemitix.tiles:enforcer:${kemitix-maven-tiles.version}
+ net.kemitix.tiles:compiler-jdk-11:${kemitix-maven-tiles.version}
+ net.kemitix.tiles:pmd:${kemitix-maven-tiles.version}
+ net.kemitix.tiles:testing:${kemitix-maven-tiles.version}
+ net.kemitix.tiles:coverage:${kemitix-maven-tiles.version}
+ net.kemitix.tiles:pitest:${kemitix-maven-tiles.version}
+ net.kemitix.checkstyle:tile:${kemitix-checkstyle.version}
+
+
+
+
+
+
+
+
diff --git a/src/main/java/net/kemitix/text/fit/TextFit.java b/src/main/java/net/kemitix/text/fit/TextFit.java
new file mode 100644
index 0000000..3ad6509
--- /dev/null
+++ b/src/main/java/net/kemitix/text/fit/TextFit.java
@@ -0,0 +1,7 @@
+package net.kemitix.text.fit;
+
+public interface TextFit {
+ static WordWrapper wrapper() {
+ return new TextLineWrapImpl();
+ }
+}
diff --git a/src/main/java/net/kemitix/text/fit/TextLineWrapImpl.java b/src/main/java/net/kemitix/text/fit/TextLineWrapImpl.java
new file mode 100644
index 0000000..3735185
--- /dev/null
+++ b/src/main/java/net/kemitix/text/fit/TextLineWrapImpl.java
@@ -0,0 +1,65 @@
+package net.kemitix.text.fit;
+
+import lombok.Getter;
+
+import java.awt.*;
+import java.awt.font.FontRenderContext;
+import java.awt.geom.Rectangle2D;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+class TextLineWrapImpl implements WordWrapper {
+
+ @Override
+ public List wrap(
+ String text,
+ Font font,
+ Graphics2D graphics2D,
+ int width
+ ) {
+ String source = String.join(" ", text.split("\n"));
+ List words = wordLengths(source.split(" "), font, graphics2D);
+ return wrapWords(words, width);
+ }
+
+ private List wrapWords(List words, int width) {
+ List lines = new ArrayList<>();
+ int end = 0;
+ List line = new ArrayList<>();
+ for (Word word : words) {
+ if ((end + word.width) > width) {
+ lines.add(String.join(" ", line));
+ line.clear();
+ end = 0;
+ }
+ line.add(word.word);
+ end += word.width;
+ }
+ lines.add(String.join(" ", line));
+ return lines.stream()
+ .filter(l -> l.length() > 0)
+ .collect(Collectors.toList());
+ }
+
+ private List wordLengths(String[] words, Font font, Graphics2D graphics2D) {
+ FontRenderContext fontRenderContext = graphics2D.getFontRenderContext();
+ return Arrays.stream(words)
+ .map(word -> new Word(word, font, fontRenderContext))
+ .collect(Collectors.toList());
+ }
+
+ @Getter
+ private static class Word {
+ private final String word;
+ private final int width;
+
+ public Word(String word, Font font, FontRenderContext fontRenderContext) {
+ this.word = word;
+ Rectangle2D stringBounds = font.getStringBounds(word + " ", fontRenderContext);
+ this.width = Double.valueOf(stringBounds.getWidth()).intValue();
+ }
+ }
+
+}
diff --git a/src/main/java/net/kemitix/text/fit/WordWrapper.java b/src/main/java/net/kemitix/text/fit/WordWrapper.java
new file mode 100644
index 0000000..a4d2a09
--- /dev/null
+++ b/src/main/java/net/kemitix/text/fit/WordWrapper.java
@@ -0,0 +1,13 @@
+package net.kemitix.text.fit;
+
+import java.awt.*;
+import java.util.List;
+
+public interface WordWrapper {
+ List wrap(
+ String text,
+ Font font,
+ Graphics2D graphics2D,
+ int width
+ );
+}
diff --git a/src/test/java/net/kemitix/text/fit/TextLineWrapTest.java b/src/test/java/net/kemitix/text/fit/TextLineWrapTest.java
new file mode 100644
index 0000000..7266db8
--- /dev/null
+++ b/src/test/java/net/kemitix/text/fit/TextLineWrapTest.java
@@ -0,0 +1,81 @@
+package net.kemitix.text.fit;
+
+import org.assertj.core.api.WithAssertions;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.List;
+
+public class TextLineWrapTest
+ implements WithAssertions {
+
+ private final WordWrapper textLineWrap = TextFit.wrapper();
+ private final int imageSize = 300;
+ private final int fontSize = 20;
+ private final Graphics2D graphics100x100 = graphics(imageSize, imageSize);
+ private final Font font;
+
+ public TextLineWrapTest()
+ throws FontFormatException,
+ IOException,
+ URISyntaxException {
+ URL resource = this.getClass().getResource("alice/Alice-Regular.ttf");
+ font = Font.createFont(Font.TRUETYPE_FONT, new File(resource.toURI()))
+ .deriveFont(Font.PLAIN, fontSize);
+ }
+
+ private List invoke(String in) {
+ return textLineWrap.wrap(in, font, graphics100x100, imageSize);
+ }
+
+ @Test
+ @DisplayName("Empty String give empty List")
+ public void emptyStringEmptyList() {
+ assertThat(invoke("")).isEmpty();
+ }
+
+ @Test
+ @DisplayName("Short string fits on one line")
+ public void shortStringOnOneLine() {
+ assertThat(invoke("x")).containsExactly("x");
+ }
+
+ @Test
+ @DisplayName("Longer string fits on two lines")
+ public void longerStringOnTwoLines() {
+ assertThat(invoke(
+ "xxxxxxxxxxx xxxxxxxxxxxx " +
+ "xxxxxxxxxxx xxxxxxxxxxxx"))
+ .containsExactly(
+ "xxxxxxxxxxx xxxxxxxxxxxx",
+ "xxxxxxxxxxx xxxxxxxxxxxx");
+ }
+
+ @Test
+ @DisplayName("Longer string fits on three lines")
+ public void longerStringOnThreeLines() {
+ assertThat(invoke(
+ "xxxxxxxxxxx xxxxxxxxxxxx " +
+ "xxxxxxxxxxx xxxxxxxxxxxx " +
+ "xxxxxxxxxxx xxxxxxxxxxxx"))
+ .containsExactly(
+ "xxxxxxxxxxx xxxxxxxxxxxx",
+ "xxxxxxxxxxx xxxxxxxxxxxx",
+ "xxxxxxxxxxx xxxxxxxxxxxx");
+ }
+
+ private Graphics2D graphics(int width, int height) {
+ return image(width, height)
+ .createGraphics();
+ }
+
+ private BufferedImage image(int width, int height) {
+ return new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
+ }
+}
diff --git a/src/test/resources/net/kemitix/text/fit/alice/Alice-Regular.ttf b/src/test/resources/net/kemitix/text/fit/alice/Alice-Regular.ttf
new file mode 100644
index 0000000..12dfdc1
Binary files /dev/null and b/src/test/resources/net/kemitix/text/fit/alice/Alice-Regular.ttf differ
diff --git a/src/test/resources/net/kemitix/text/fit/alice/FONTLOG.txt b/src/test/resources/net/kemitix/text/fit/alice/FONTLOG.txt
new file mode 100644
index 0000000..8dade5f
--- /dev/null
+++ b/src/test/resources/net/kemitix/text/fit/alice/FONTLOG.txt
@@ -0,0 +1,50 @@
+FONTLOG for the Alice font
+
+This file provides detailed information on the Alice font Software.
+
+This information should be distributed along with the
+Alice fonts and any derivative works.
+
+Basic Font Information
+
+Ksenia Erulevich, designer of Alice typeface,
+was inspired by Lewis Carroll's novel and
+decided to make a typeface that will
+be suitable for typesetting this book.
+
+It came out eclectic and quaint, old-fashioned,
+having widened proportions, open aperture, and
+soft rounded features. Perfect for long meditative text-setting
+and headlines.
+
+This is in fact Ksenia's first typeface, as part of
+her diploma project at the Type & Typography course in
+Moscow, Russia.
+
+Released by Cyreal with help from Gayaneh Bagdasaryan
+and Alexei Vanyashin.
+
+Alice is a Unicode typeface family that supports
+languages that use the Latin script and its variants, and
+could be expanded to support other scripts.
+
+More specifically, this release supports the following Unicode
+ranges: Latin-1
+
+To contribute to the project contact Alexei Vanyashin at
+a@cyreal.org
+
+There are two .VBF Source files:
+1. Alice_PS_Source.vbf (Original Source files with contour overlaps)
+2. Alice_merged_and_optimized.vbf (Merged and optimized file)
+
+ChangeLog
+
+If you make modifications be sure to add your name (N),
+email (E), web-address (if you have one) (W) and description (D).
+This list is in alphabetical order.
+
+N: Alexei Vanyashin
+E: a@cyreal.org
+W: www.cyreal.org
+D: Designer and Mastering
\ No newline at end of file
diff --git a/src/test/resources/net/kemitix/text/fit/alice/OFL.txt b/src/test/resources/net/kemitix/text/fit/alice/OFL.txt
new file mode 100644
index 0000000..636f1b4
--- /dev/null
+++ b/src/test/resources/net/kemitix/text/fit/alice/OFL.txt
@@ -0,0 +1,94 @@
+Copyright (c) 2011, Cyreal (www.cyreal.org),
+with Reserved Font Name "Alice" and "Alice Regular".
+
+This Font Software is licensed under the SIL Open Font License, Version 1.1.
+This license is copied below, and is also available with a FAQ at:
+http://scripts.sil.org/OFL
+
+
+-----------------------------------------------------------
+SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
+-----------------------------------------------------------
+
+PREAMBLE
+The goals of the Open Font License (OFL) are to stimulate worldwide
+development of collaborative font projects, to support the font creation
+efforts of academic and linguistic communities, and to provide a free and
+open framework in which fonts may be shared and improved in partnership
+with others.
+
+The OFL allows the licensed fonts to be used, studied, modified and
+redistributed freely as long as they are not sold by themselves. The
+fonts, including any derivative works, can be bundled, embedded,
+redistributed and/or sold with any software provided that any reserved
+names are not used by derivative works. The fonts and derivatives,
+however, cannot be released under any other type of license. The
+requirement for fonts to remain under this license does not apply
+to any document created using the fonts or their derivatives.
+
+DEFINITIONS
+"Font Software" refers to the set of files released by the Copyright
+Holder(s) under this license and clearly marked as such. This may
+include source files, build scripts and documentation.
+
+"Reserved Font Name" refers to any names specified as such after the
+copyright statement(s).
+
+"Original Version" refers to the collection of Font Software components as
+distributed by the Copyright Holder(s).
+
+"Modified Version" refers to any derivative made by adding to, deleting,
+or substituting -- in part or in whole -- any of the components of the
+Original Version, by changing formats or by porting the Font Software to a
+new environment.
+
+"Author" refers to any designer, engineer, programmer, technical
+writer or other person who contributed to the Font Software.
+
+PERMISSION & CONDITIONS
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the Font Software, to use, study, copy, merge, embed, modify,
+redistribute, and sell modified and unmodified copies of the Font
+Software, subject to the following conditions:
+
+1) Neither the Font Software nor any of its individual components,
+in Original or Modified Versions, may be sold by itself.
+
+2) Original or Modified Versions of the Font Software may be bundled,
+redistributed and/or sold with any software, provided that each copy
+contains the above copyright notice and this license. These can be
+included either as stand-alone text files, human-readable headers or
+in the appropriate machine-readable metadata fields within text or
+binary files as long as those fields can be easily viewed by the user.
+
+3) No Modified Version of the Font Software may use the Reserved Font
+Name(s) unless explicit written permission is granted by the corresponding
+Copyright Holder. This restriction only applies to the primary font name as
+presented to the users.
+
+4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
+Software shall not be used to promote, endorse or advertise any
+Modified Version, except to acknowledge the contribution(s) of the
+Copyright Holder(s) and the Author(s) or with their explicit written
+permission.
+
+5) The Font Software, modified or unmodified, in part or in whole,
+must be distributed entirely under this license, and must not be
+distributed under any other license. The requirement for fonts to
+remain under this license does not apply to any document created
+using the Font Software.
+
+TERMINATION
+This license becomes null and void if any of the above conditions are
+not met.
+
+DISCLAIMER
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+OTHER DEALINGS IN THE FONT SOFTWARE.