diff --git a/com.io7m.jsycamore.api/src/main/java/com/io7m/jsycamore/api/components/SyTextMultiLineViewReadableType.java b/com.io7m.jsycamore.api/src/main/java/com/io7m/jsycamore/api/components/SyTextMultiLineViewReadableType.java index 04303d7a..03d7a138 100644 --- a/com.io7m.jsycamore.api/src/main/java/com/io7m/jsycamore/api/components/SyTextMultiLineViewReadableType.java +++ b/com.io7m.jsycamore.api/src/main/java/com/io7m/jsycamore/api/components/SyTextMultiLineViewReadableType.java @@ -19,10 +19,11 @@ import com.io7m.jattribute.core.AttributeReadableType; import com.io7m.jsycamore.api.layout.SyLayoutContextType; import com.io7m.jsycamore.api.text.SyTextLineMeasuredType; +import com.io7m.jsycamore.api.text.SyTextLinePositioned; import com.io7m.jsycamore.api.themes.SyThemeClassNameType; import java.util.List; -import java.util.SortedMap; +import java.util.Optional; import static com.io7m.jsycamore.api.themes.SyThemeClassNameStandard.TEXT_MULTILINE_VIEW; @@ -55,10 +56,10 @@ default List themeClassesDefaultForComponent() } /** - * @return A read-only snapshot of the texts by Y offset + * @return A read-only snapshot of the positioned lines of text */ - SortedMap textsByYOffset(); + Iterable textLinesPositioned(); /** * Determine the minimum size on the Y axis required to display the @@ -70,4 +71,12 @@ default List themeClassesDefaultForComponent() */ int minimumSizeYRequired(SyLayoutContextType layoutContext); + + /** + * @param y The Y offset + * + * @return The line starting at Y offset {@code y} + */ + + Optional textByYOffset(int y); } diff --git a/com.io7m.jsycamore.api/src/main/java/com/io7m/jsycamore/api/spaces/SySpaceTextType.java b/com.io7m.jsycamore.api/src/main/java/com/io7m/jsycamore/api/spaces/SySpaceTextAlignedType.java similarity index 94% rename from com.io7m.jsycamore.api/src/main/java/com/io7m/jsycamore/api/spaces/SySpaceTextType.java rename to com.io7m.jsycamore.api/src/main/java/com/io7m/jsycamore/api/spaces/SySpaceTextAlignedType.java index 9411af9e..c8636510 100644 --- a/com.io7m.jsycamore.api/src/main/java/com/io7m/jsycamore/api/spaces/SySpaceTextType.java +++ b/com.io7m.jsycamore.api/src/main/java/com/io7m/jsycamore/api/spaces/SySpaceTextAlignedType.java @@ -22,7 +22,7 @@ * text direction (left-to-right vs right-to-left, for example). */ -public interface SySpaceTextType extends SySpaceType +public interface SySpaceTextAlignedType extends SySpaceType { // No value-level representation. } diff --git a/com.io7m.jsycamore.api/src/main/java/com/io7m/jsycamore/api/text/SyFontType.java b/com.io7m.jsycamore.api/src/main/java/com/io7m/jsycamore/api/text/SyFontType.java index 444ef4ee..5e7028ae 100644 --- a/com.io7m.jsycamore.api/src/main/java/com/io7m/jsycamore/api/text/SyFontType.java +++ b/com.io7m.jsycamore.api/src/main/java/com/io7m/jsycamore/api/text/SyFontType.java @@ -64,10 +64,9 @@ public interface SyFontType * Split the given text into lines based on the given page width; lines will * be broken in order to ensure that text fits within the page width. * - * @param textID The text identifier - * @param text The text - * @param firstLineNumber The number of the first line - * @param pageWidth The page width + * @param textID The text identifier + * @param text The text + * @param pageWidth The page width * * @return The non-empty list of split lines */ @@ -75,32 +74,25 @@ public interface SyFontType default List textLayout( final SyTextID textID, final SyText text, - final SyTextLineNumber firstLineNumber, final int pageWidth) { final var m = new TreeMap(); m.put(textID, text); - return this.textLayoutMultiple( - m, - firstLineNumber, - pageWidth - ); + return this.textLayoutMultiple(m, pageWidth); } /** * Split the given texts into lines based on the given page width; lines will * be broken in order to ensure that text fits within the page width. * - * @param texts The texts - * @param firstLineNumber The number of the first line - * @param pageWidth The page width + * @param texts The texts + * @param pageWidth The page width * * @return The split lines */ List textLayoutMultiple( SortedMap texts, - SyTextLineNumber firstLineNumber, int pageWidth ); } diff --git a/com.io7m.jsycamore.api/src/main/java/com/io7m/jsycamore/api/text/SyTextLineMeasuredType.java b/com.io7m.jsycamore.api/src/main/java/com/io7m/jsycamore/api/text/SyTextLineMeasuredType.java index 1a721c92..8269ef08 100644 --- a/com.io7m.jsycamore.api/src/main/java/com/io7m/jsycamore/api/text/SyTextLineMeasuredType.java +++ b/com.io7m.jsycamore.api/src/main/java/com/io7m/jsycamore/api/text/SyTextLineMeasuredType.java @@ -17,9 +17,8 @@ package com.io7m.jsycamore.api.text; -import com.io7m.jregions.core.parameterized.sizes.PAreaSizeI; import com.io7m.jsycamore.api.spaces.SySpaceParentRelativeType; -import com.io7m.jsycamore.api.spaces.SySpaceTextType; +import com.io7m.jsycamore.api.spaces.SySpaceTextAlignedType; import com.io7m.jtensors.core.parameterized.vectors.PVector2I; /** @@ -33,23 +32,22 @@ public interface SyTextLineMeasuredType { /** - * The bounds in terms of a line on a page: The bounds with a height equal - * to the text, and the width equal to the page width. - * - * @return The size of the page line + * @return The width of the page within this line resides */ - PAreaSizeI pageLineBounds(); + int pageWidth(); /** - * The smallest bounds that can contain the given text. An area of this - * size will be positioned somewhere within an area of size {@link #pageLineBounds()} - * for rendering and mouse interactions. - * - * @return The size of the text line + * @return The height required to contain this text + */ + + int height(); + + /** + * @return The smallest width that can contain the given text. */ - PAreaSizeI textBounds(); + int textWidth(); /** * @return The original text that produced this line @@ -66,15 +64,18 @@ public interface SyTextLineMeasuredType /** * Inspect the text at the given position. The information returned includes * details such as the index of the character within the string at the given - * location, information for rendering a caret, etc. + * location, information for rendering a caret, etc. The location will be + * reported to be at line {@code lineNumber}. * - * @param position The position + * @param lineNumber The line number + * @param position The position * * @return Information about text at the given position */ SyTextLocationType inspectAt( - PVector2I position); + SyTextLineNumber lineNumber, + PVector2I position); /** * Transform the given parent-relative coordinates to text-space coordinates. @@ -84,28 +85,26 @@ SyTextLocationType inspectAt( * @return The equivalent text-space position */ - PVector2I transformToTextCoordinates( + PVector2I transformToTextCoordinates( PVector2I position); /** - * Inspect the character at the given parent relative location. + * Inspect the character at the given parent relative location. The location + * will be reported to be at line {@code lineNumber}. * - * @param position The parent-relative position + * @param lineNumber The line number + * @param position The parent-relative position * * @return The character at the given location */ default SyTextLocationType inspectAtParentRelative( + final SyTextLineNumber lineNumber, final PVector2I position) { return this.inspectAt( + lineNumber, this.transformToTextCoordinates(position) ); } - - /** - * @return The line number - */ - - SyTextLineNumber lineNumber(); } diff --git a/com.io7m.jsycamore.api/src/main/java/com/io7m/jsycamore/api/text/SyTextLinePositioned.java b/com.io7m.jsycamore.api/src/main/java/com/io7m/jsycamore/api/text/SyTextLinePositioned.java new file mode 100644 index 00000000..96f92bc7 --- /dev/null +++ b/com.io7m.jsycamore.api/src/main/java/com/io7m/jsycamore/api/text/SyTextLinePositioned.java @@ -0,0 +1,48 @@ +/* + * Copyright © 2023 Mark Raynsford https://www.io7m.com + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR + * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + + +package com.io7m.jsycamore.api.text; + +import java.util.Objects; + +/** + * A positioned text line produced from a text. + * + * @param y The y position + * @param textLineNumber The line number + * @param textLine The line + */ + +public record SyTextLinePositioned( + int y, + SyTextLineNumber textLineNumber, + SyTextLineMeasuredType textLine) +{ + /** + * A positioned text line produced from a text. + * + * @param y The y position + * @param textLineNumber The line number + * @param textLine The line + */ + + public SyTextLinePositioned + { + Objects.requireNonNull(textLineNumber, "textLineNumber"); + Objects.requireNonNull(textLine, "textLine"); + } +} diff --git a/com.io7m.jsycamore.api/src/main/java/com/io7m/jsycamore/api/text/SyTextMultiLineModelReadableType.java b/com.io7m.jsycamore.api/src/main/java/com/io7m/jsycamore/api/text/SyTextMultiLineModelReadableType.java index 8c1fd1d9..e151cf1c 100644 --- a/com.io7m.jsycamore.api/src/main/java/com/io7m/jsycamore/api/text/SyTextMultiLineModelReadableType.java +++ b/com.io7m.jsycamore.api/src/main/java/com/io7m/jsycamore/api/text/SyTextMultiLineModelReadableType.java @@ -58,14 +58,6 @@ public interface SyTextMultiLineModelReadableType Optional textSectionContainingLine( SyTextLineNumber lineNumber); - /** - * The set of measured lines with their associated Y coordinate. - * - * @return The measured lines - */ - - SortedMap linesByYCoordinate(); - /** * Inspect the text at the given position. The information returned includes * details such as the index of the character within the string at the given @@ -88,4 +80,18 @@ Optional inspectAt( */ int minimumSizeYRequired(); + + /** + * @return A read-only snapshot of the positioned lines of text + */ + + Iterable textLinesPositioned(); + + /** + * @param y The y offset + * + * @return The measured line that starts at Y offset {@code y} + */ + + Optional textByYOffset(int y); } diff --git a/com.io7m.jsycamore.awt/src/main/java/com/io7m/jsycamore/awt/internal/SyAWTFont.java b/com.io7m.jsycamore.awt/src/main/java/com/io7m/jsycamore/awt/internal/SyAWTFont.java index 34fb98bf..0fcc599d 100644 --- a/com.io7m.jsycamore.awt/src/main/java/com/io7m/jsycamore/awt/internal/SyAWTFont.java +++ b/com.io7m.jsycamore.awt/src/main/java/com/io7m/jsycamore/awt/internal/SyAWTFont.java @@ -17,13 +17,11 @@ package com.io7m.jsycamore.awt.internal; -import com.io7m.jregions.core.parameterized.sizes.PAreaSizeI; import com.io7m.jsycamore.api.text.SyFontDescription; import com.io7m.jsycamore.api.text.SyFontType; import com.io7m.jsycamore.api.text.SyText; import com.io7m.jsycamore.api.text.SyTextID; import com.io7m.jsycamore.api.text.SyTextLineMeasuredType; -import com.io7m.jsycamore.api.text.SyTextLineNumber; import java.awt.Font; import java.awt.FontMetrics; @@ -96,7 +94,6 @@ public SyFontDescription description() @Override public List textLayoutMultiple( final SortedMap texts, - final SyTextLineNumber firstLineNumber, final int pageWidth) { Objects.requireNonNull(texts, "texts"); @@ -108,8 +105,6 @@ public List textLayoutMultiple( final var results = new LinkedList(); - var lineNumber = firstLineNumber; - for (final var textEntry : texts.entrySet()) { final var textID = textEntry.getKey(); @@ -117,8 +112,7 @@ public List textLayoutMultiple( textEntry.getValue(); if (text.value().isEmpty()) { - results.add(this.emptySectionLine(pageWidth, textID, lineNumber)); - lineNumber = lineNumber.next(); + results.add(this.emptySectionLine(pageWidth, textID)); continue; } @@ -162,8 +156,8 @@ public List textLayoutMultiple( final var line = new SyAWTTextAnalyzed( pageWidth, - PAreaSizeI.of(textWidth, this.textHeight()), - lineNumber, + textWidth, + this.textHeight(), layout, textID, new SyText(brokenText, text.direction()) @@ -171,7 +165,6 @@ public List textLayoutMultiple( results.add(line); indexThen = indexNow; - lineNumber = lineNumber.next(); } } @@ -180,13 +173,12 @@ public List textLayoutMultiple( private SyTextLineMeasuredType emptySectionLine( final int pageWidth, - final SyTextID textID, - final SyTextLineNumber lineNumber) + final SyTextID textID) { return new SyAWTTextAnalyzed( pageWidth, - PAreaSizeI.of(0, this.textHeight()), - lineNumber, + 0, + this.textHeight(), new TextLayout(" ", this.font, this.metrics.getFontRenderContext()), textID, SyText.empty() diff --git a/com.io7m.jsycamore.awt/src/main/java/com/io7m/jsycamore/awt/internal/SyAWTTextAnalyzed.java b/com.io7m.jsycamore.awt/src/main/java/com/io7m/jsycamore/awt/internal/SyAWTTextAnalyzed.java index a04920ce..9ef71445 100644 --- a/com.io7m.jsycamore.awt/src/main/java/com/io7m/jsycamore/awt/internal/SyAWTTextAnalyzed.java +++ b/com.io7m.jsycamore.awt/src/main/java/com/io7m/jsycamore/awt/internal/SyAWTTextAnalyzed.java @@ -19,9 +19,8 @@ import com.io7m.jregions.core.parameterized.areas.PAreaI; import com.io7m.jregions.core.parameterized.areas.PAreasI; -import com.io7m.jregions.core.parameterized.sizes.PAreaSizeI; import com.io7m.jsycamore.api.spaces.SySpaceParentRelativeType; -import com.io7m.jsycamore.api.spaces.SySpaceTextType; +import com.io7m.jsycamore.api.spaces.SySpaceTextAlignedType; import com.io7m.jsycamore.api.text.SyText; import com.io7m.jsycamore.api.text.SyTextID; import com.io7m.jsycamore.api.text.SyTextLineMeasuredType; @@ -41,58 +40,61 @@ public final class SyAWTTextAnalyzed implements SyTextLineMeasuredType { private final int pageWidth; - private final PAreaSizeI textBounds; private final TextLayout layout; - private final SyTextLineNumber lineNumber; private final SyText textAsWrapped; - private final PAreaSizeI pageBounds; private final SyTextID textOriginal; + private final int textHeight; + private final int textWidth; /** * Create a section of analyzed text. * - * @param inPageWidth The page width - * @param inTextBounds The text bounds - * @param inLineNumber The line number - * @param inLayout The text layout - * @param inTextOriginal The original text that was analyzed - * @param inTextAsWrapped The text after analysis and wrapping + * @param inPageWidth The page width + * @param inTextWidth The text width + * @param inTextHeight The text height + * @param inLayout The text layout + * @param inTextOriginal The original text that was analyzed + * @param inTextAsWrapped The text after analysis and wrapping */ public SyAWTTextAnalyzed( final int inPageWidth, - final PAreaSizeI inTextBounds, - final SyTextLineNumber inLineNumber, + final int inTextWidth, + final int inTextHeight, final TextLayout inLayout, final SyTextID inTextOriginal, final SyText inTextAsWrapped) { this.pageWidth = inPageWidth; - this.textBounds = - Objects.requireNonNull(inTextBounds, "size"); + this.textWidth = + inTextWidth; + this.textHeight = + inTextHeight; this.textOriginal = Objects.requireNonNull(inTextOriginal, "inTextOriginal"); this.textAsWrapped = Objects.requireNonNull(inTextAsWrapped, "text"); this.layout = Objects.requireNonNull(inLayout, "inLayout"); - this.pageBounds = - PAreaSizeI.of(inPageWidth, this.textBounds.sizeY()); - this.lineNumber = - Objects.requireNonNull(inLineNumber, "inLineNumber"); } @Override - public PAreaSizeI pageLineBounds() + public int pageWidth() { - return this.pageBounds; + return this.pageWidth; } @Override - public PAreaSizeI textBounds() + public int height() { - return this.textBounds; + return this.textHeight; + } + + @Override + public int textWidth() + { + return this.textWidth; } @Override @@ -109,8 +111,10 @@ public SyText textAsWrapped() @Override public SyTextLocationType inspectAt( - final PVector2I position) + final SyTextLineNumber lineNumber, + final PVector2I position) { + Objects.requireNonNull(lineNumber, "lineNumber"); Objects.requireNonNull(position, "position"); final var hitInfo = @@ -128,11 +132,11 @@ public SyTextLocationType inspectAt( caretBounds.height ); - return new SyTextLocation(this, hitInfo, caretArea); + return new SyTextLocation(this, lineNumber, hitInfo, caretArea); } @Override - public PVector2I transformToTextCoordinates( + public PVector2I transformToTextCoordinates( final PVector2I position) { Objects.requireNonNull(position, "position"); @@ -151,32 +155,30 @@ public PVector2I transformToTextCoordinates( */ case TEXT_DIRECTION_RIGHT_TO_LEFT -> { - final var delta = this.pageWidth - this.textBounds.sizeX(); + final var delta = this.pageWidth - this.textWidth; yield PVector2I.of(position.x() - delta, position.y()); } }; } - @Override - public SyTextLineNumber lineNumber() - { - return this.lineNumber; - } - private static final class SyTextLocation implements SyTextLocationType { private final SyAWTTextAnalyzed owner; + private final SyTextLineNumber lineNumber; private final TextHitInfo hitInfo; private final SyCaret caret; private SyTextLocation( final SyAWTTextAnalyzed inOwner, + final SyTextLineNumber inLineNumber, final TextHitInfo inHitInfo, final PAreaI inArea) { this.owner = Objects.requireNonNull(inOwner, "owner"); + this.lineNumber = + Objects.requireNonNull(inLineNumber, "lineNumber"); this.hitInfo = Objects.requireNonNull(inHitInfo, "hitInfo"); this.caret = @@ -186,7 +188,7 @@ private SyTextLocation( @Override public SyTextLineNumber lineNumber() { - return this.owner.lineNumber; + return this.lineNumber; } @Override diff --git a/com.io7m.jsycamore.components.standard/src/main/java/com/io7m/jsycamore/components/standard/text/SyLineMap.java b/com.io7m.jsycamore.components.standard/src/main/java/com/io7m/jsycamore/components/standard/text/SyLineMap.java new file mode 100644 index 00000000..56040bb4 --- /dev/null +++ b/com.io7m.jsycamore.components.standard/src/main/java/com/io7m/jsycamore/components/standard/text/SyLineMap.java @@ -0,0 +1,92 @@ +/* + * Copyright © 2023 Mark Raynsford https://www.io7m.com + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR + * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + + +package com.io7m.jsycamore.components.standard.text; + +import com.io7m.jsycamore.api.text.SyTextID; +import com.io7m.jsycamore.api.text.SyTextLineNumber; + +import java.util.Collections; +import java.util.Objects; +import java.util.Optional; +import java.util.SortedSet; +import java.util.TreeMap; +import java.util.TreeSet; + +/** + * A multimap from text IDs to sets of line numbers. + */ + +final class SyLineMap +{ + private final TreeMap> items; + + SyLineMap() + { + this.items = new TreeMap<>(); + } + + SortedSet linesForText( + final SyTextID id) + { + return Optional.ofNullable(this.items.get(id)) + .orElse(Collections.emptySortedSet()); + } + + void clear() + { + this.items.clear(); + } + + void lineAssociate( + final SyTextID id, + final SyTextLineNumber lineNumber) + { + Objects.requireNonNull(id, "id"); + Objects.requireNonNull(lineNumber, "lineNumber"); + + var lines = this.items.get(id); + if (lines == null) { + lines = new TreeSet<>(); + } + lines.add(lineNumber); + this.items.put(id, lines); + } + + void lineDisassociate( + final SyTextID id, + final SyTextLineNumber lineNumber) + { + Objects.requireNonNull(id, "id"); + Objects.requireNonNull(lineNumber, "lineNumber"); + + final var lines = this.items.get(id); + if (lines == null) { + return; + } + lines.remove(lineNumber); + this.items.put(id, lines); + } + + SortedSet lineDisassociateAll( + final SyTextID id) + { + Objects.requireNonNull(id, "id"); + return Optional.ofNullable(this.items.remove(id)) + .orElse(Collections.emptySortedSet()); + } +} diff --git a/com.io7m.jsycamore.components.standard/src/main/java/com/io7m/jsycamore/components/standard/text/SyLineYBiMap.java b/com.io7m.jsycamore.components.standard/src/main/java/com/io7m/jsycamore/components/standard/text/SyLineYBiMap.java new file mode 100644 index 00000000..ec6e345b --- /dev/null +++ b/com.io7m.jsycamore.components.standard/src/main/java/com/io7m/jsycamore/components/standard/text/SyLineYBiMap.java @@ -0,0 +1,113 @@ +/* + * Copyright © 2023 Mark Raynsford https://www.io7m.com + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR + * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + + +package com.io7m.jsycamore.components.standard.text; + +import com.io7m.jsycamore.api.text.SyTextLineNumber; + +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.Optional; +import java.util.TreeMap; + +/** + * A bidirectional map between line numbers and Y offsets. + */ + +final class SyLineYBiMap +{ + private final TreeMap lineToY; + private final TreeMap yToLine; + + SyLineYBiMap() + { + this.lineToY = new TreeMap<>(); + this.yToLine = new TreeMap<>(); + } + + void clear() + { + this.lineToY.clear(); + this.yToLine.clear(); + } + + void set( + final SyTextLineNumber lineNumber, + final int y) + { + Objects.requireNonNull(lineNumber, "lineNumber"); + + final var boxY = Integer.valueOf(y); + this.removeByLine(lineNumber); + this.removeByY(y); + + this.lineToY.put(lineNumber, boxY); + this.yToLine.put(boxY, lineNumber); + } + + Optional lineContainingY( + final int y) + { + return Optional.ofNullable(this.yToLine.floorEntry(Integer.valueOf(y))) + .map(Map.Entry::getValue); + } + + Optional highestY() + { + try { + return Optional.of(this.yToLine.lastKey()); + } catch (final NoSuchElementException e) { + return Optional.empty(); + } + } + + Optional removeByLine( + final SyTextLineNumber line) + { + Objects.requireNonNull(line, "line"); + + final var y = this.lineToY.remove(line); + if (y != null) { + this.yToLine.remove(y); + } + return Optional.ofNullable(y); + } + + Optional removeByY( + final int y) + { + final var line = this.yToLine.remove(Integer.valueOf(y)); + if (line != null) { + this.lineToY.remove(line); + } + return Optional.ofNullable(line); + } + + Optional line( + final int y) + { + return Optional.ofNullable(this.yToLine.get(Integer.valueOf(y))); + } + + Optional y( + final SyTextLineNumber lineNumber) + { + Objects.requireNonNull(lineNumber, "lineNumber"); + return Optional.ofNullable(this.lineToY.get(lineNumber)); + } +} diff --git a/com.io7m.jsycamore.components.standard/src/main/java/com/io7m/jsycamore/components/standard/text/SyTextMultiLineModel.java b/com.io7m.jsycamore.components.standard/src/main/java/com/io7m/jsycamore/components/standard/text/SyTextMultiLineModel.java index 1329ccfe..0f01878f 100644 --- a/com.io7m.jsycamore.components.standard/src/main/java/com/io7m/jsycamore/components/standard/text/SyTextMultiLineModel.java +++ b/com.io7m.jsycamore.components.standard/src/main/java/com/io7m/jsycamore/components/standard/text/SyTextMultiLineModel.java @@ -28,6 +28,7 @@ import com.io7m.jsycamore.api.text.SyTextID; import com.io7m.jsycamore.api.text.SyTextLineMeasuredType; import com.io7m.jsycamore.api.text.SyTextLineNumber; +import com.io7m.jsycamore.api.text.SyTextLinePositioned; import com.io7m.jsycamore.api.text.SyTextLocationType; import com.io7m.jsycamore.api.text.SyTextMultiLineModelType; import com.io7m.jsycamore.api.text.SyTextSelection; @@ -35,9 +36,10 @@ import com.io7m.jtensors.core.parameterized.vectors.PVector2I; import java.util.Collections; -import java.util.HashMap; +import java.util.Iterator; import java.util.LinkedList; import java.util.List; +import java.util.Map; import java.util.NoSuchElementException; import java.util.Objects; import java.util.Optional; @@ -52,10 +54,10 @@ public final class SyTextMultiLineModel implements SyTextMultiLineModelType { private final AttributeType pageWidth; private final TreeMap textSections; - private final TreeMap textMeasuredLinesByY; - private final TreeMap textLineNumberToY; + private final TreeMap textLines; + private final SyLineYBiMap textLinesY; private final AttributeType font; - private final HashMap> textSectionsToMeasuredLines; + private final SyLineMap textSectionsToLines; private final SortedMap textSectionsReadable; private SySelectionState selectionState; @@ -74,12 +76,12 @@ private SyTextMultiLineModel( new TreeMap<>(); this.textSectionsReadable = Collections.unmodifiableSortedMap(this.textSections); - this.textSectionsToMeasuredLines = - new HashMap<>(); - this.textMeasuredLinesByY = - new TreeMap<>(); - this.textLineNumberToY = + this.textSectionsToLines = + new SyLineMap(); + this.textLines = new TreeMap<>(); + this.textLinesY = + new SyLineYBiMap(); /* * Changing the page width or the font will require re-measuring all @@ -88,34 +90,16 @@ private SyTextMultiLineModel( this.pageWidth.subscribe((oldValue, newValue) -> { if (!Objects.equals(oldValue, newValue)) { - this.regenerate(SyRegenerateAll.REGENERATE_ALL); + this.edit(SyEditOpRegenerate.SY_EDIT_OP_REGENERATE); } }); this.font.subscribe((oldValue, newValue) -> { if (!Objects.equals(oldValue, newValue)) { - this.regenerate(SyRegenerateAll.REGENERATE_ALL); + this.edit(SyEditOpRegenerate.SY_EDIT_OP_REGENERATE); } }); } - private sealed interface SyRegenerateType - { - - } - - private enum SyRegenerateAll - implements SyRegenerateType - { - REGENERATE_ALL - } - - private record SyRegenerateFrom( - SyTextID textSectionIndex) - implements SyRegenerateType - { - - } - /** * Create a new multi-line text model. * @@ -154,142 +138,79 @@ private static PAreaI sanitizedArea( ); } - private void regenerate( - final SyRegenerateType regenerate) + private void edit( + final SyEditOpType edit) + { + switch (edit) { + case final SyEditOpRegenerate op -> this.editRegenerate(); + case final SyEditOpAppend op -> this.editAppend(op); + } + } + + private void editAppend( + final SyEditOpAppend op) { final var fontNow = this.font.get(); final var wrapNow = this.pageWidth.get().intValue(); - switch (regenerate) { - case final SyRegenerateAll all -> { - this.regenerateEverything(fontNow, wrapNow); - } - case final SyRegenerateFrom from -> { - this.regenerateFrom(from, fontNow, wrapNow); - } + final var newSections = + this.textSections.tailMap(op.textID, true); + final var newMeasured = + fontNow.textLayoutMultiple(newSections, wrapNow); + + var lineNumber = this.nextFreeLineNumber(); + var y = this.highestYOffset(); + for (final var measured : newMeasured) { + this.textLines.put(lineNumber, measured); + this.textLinesY.set(lineNumber, y); + this.textSectionsToLines.lineAssociate( + measured.textOriginal(), + lineNumber); + + y += measured.height(); + lineNumber = lineNumber.next(); } } - private void regenerateFrom( - final SyRegenerateFrom from, - final SyFontType fontNow, - final int wrapNow) + private SyTextLineNumber nextFreeLineNumber() { - if (Objects.equals(from.textSectionIndex, SyTextID.first())) { - this.regenerateEverything(fontNow, wrapNow); - return; - } - - final var textSectionsToRedo = - this.textSections.tailMap( - from.textSectionIndex, - true - ); - - /* - * Remove all the data associated with the text sections that will - * be re-measured. - */ - - for (final var entry : textSectionsToRedo.entrySet()) { - final var textID = - entry.getKey(); - final var lines = - this.textSectionsToMeasuredLines.get(textID); - - if (lines == null) { - continue; - } - - for (final var line : lines) { - final var lineNumber = line.lineNumber(); - final var y = this.textLineNumberToY.get(lineNumber); - if (y == null) { - this.textMeasuredLinesByY.remove(y); - } - this.textLineNumberToY.remove(lineNumber); - } - } - - /* - * Determine what the new starting line number will be. - */ - - SyTextLineNumber startingLineNumber; try { - startingLineNumber = - this.textLineNumberToY.lastKey(); + return this.textLines.lastKey().next(); } catch (final NoSuchElementException e) { - startingLineNumber = - new SyTextLineNumber(0); - } - - final var newMeasured = - fontNow.textLayoutMultiple( - textSectionsToRedo, - startingLineNumber, - wrapNow - ); - - var y = this.highestYOffset(); - for (final var measured : newMeasured) { - final var boxY = Integer.valueOf(y); - this.recordMeasuredLineForText(measured); - this.textMeasuredLinesByY.put(boxY, measured); - this.textLineNumberToY.put(measured.lineNumber(), boxY); - final var textSize = measured.textBounds(); - y += textSize.sizeY(); + return SyTextLineNumber.first(); } } - private void regenerateEverything( - final SyFontType fontNow, - final int wrapNow) + private void editRegenerate() { - this.textMeasuredLinesByY.clear(); - this.textSectionsToMeasuredLines.clear(); - this.textLineNumberToY.clear(); + this.textSectionsToLines.clear(); + this.textLines.clear(); + this.textLinesY.clear(); + + final var fontNow = + this.font.get(); + final var wrapNow = + this.pageWidth.get().intValue(); final var newMeasured = - fontNow.textLayoutMultiple( - this.textSections, - SyTextLineNumber.first(), - wrapNow - ); + fontNow.textLayoutMultiple(this.textSections, wrapNow); + var lineNumber = SyTextLineNumber.first(); var y = 0; for (final var measured : newMeasured) { - final var boxY = Integer.valueOf(y); - this.recordMeasuredLineForText(measured); - this.textMeasuredLinesByY.put(boxY, measured); - this.textLineNumberToY.put(measured.lineNumber(), boxY); - final var textSize = measured.textBounds(); - y += textSize.sizeY(); + this.textLines.put(lineNumber, measured); + this.textLinesY.set(lineNumber, y); + this.textSectionsToLines.lineAssociate( + measured.textOriginal(), + lineNumber); + + y += measured.height(); + lineNumber = lineNumber.next(); } } - /** - * Record the relationship between the original text and a - * measured line that it produced. - */ - - private void recordMeasuredLineForText( - final SyTextLineMeasuredType measured) - { - var linesForText = - this.textSectionsToMeasuredLines.get(measured.textOriginal()); - if (linesForText == null) { - linesForText = new LinkedList<>(); - } - linesForText.add(measured); - this.textSectionsToMeasuredLines.put( - measured.textOriginal(), - linesForText - ); - } - @Override public void setFont( final SyFontType newFont) @@ -319,14 +240,13 @@ public void textSectionsAppend( nextID = SyTextID.first(); } - final SyTextID regenerateFrom = nextID; for (final var section : sections) { this.textSections.put(nextID, section); nextID = nextID.next(); } - this.regenerate(new SyRegenerateFrom(regenerateFrom)); + this.edit(new SyEditOpAppend(regenerateFrom)); } private List> buildRegions( @@ -381,12 +301,14 @@ private List> buildRegions( final var textDirection = line.textAsWrapped().direction(); final var y = - this.textLineNumberToY.get(lineNumber) + this.textLinesY.y(lineNumber) + .orElseThrow() .intValue(); + final var textSizeX = - line.textBounds().sizeX(); + line.textWidth(); final var textSizeY = - line.textBounds().sizeY(); + line.height(); /* * Lines that are not the first or last line are fully selected. @@ -571,12 +493,13 @@ yield sanitizedArea( final var textDirection = line.textAsWrapped().direction(); final var y = - this.textLineNumberToY.get(lineNumber) + this.textLinesY.y(lineNumber) + .orElseThrow() .intValue(); final var textSizeX = - line.textBounds().sizeX(); + line.textWidth(); final var textSizeY = - line.textBounds().sizeY(); + line.height(); /* * Lines that are not the first or last line are fully selected. @@ -741,9 +664,7 @@ yield sanitizedArea( private SyTextLineMeasuredType textForLine( final SyTextLineNumber lineNumber) { - return this.textMeasuredLinesByY.get( - this.textLineNumberToY.get(lineNumber) - ); + return this.textLines.get(lineNumber); } @Override @@ -834,16 +755,40 @@ public Optional selectionFinish( )); } - private int highestYOffset() + @Override + public Iterable textLinesPositioned() + { + final Iterator> baseIterator = + this.textLines.entrySet().iterator(); + + return () -> new MappedIterator(this, baseIterator); + } + + @Override + public Optional textByYOffset( + final int y) { - final var entry = - this.textMeasuredLinesByY.lastEntry(); + return this.textLinesY.line(y) + .flatMap(n -> Optional.ofNullable(this.textLines.get(n))); + } - if (entry == null) { + private int highestYOffset() + { + final var highestYOpt = this.textLinesY.highestY(); + if (highestYOpt.isEmpty()) { return 0; } - return entry.getKey().intValue() + entry.getValue().textBounds().sizeY(); + final var highestY = + highestYOpt.get().intValue(); + final var lineNumber = + this.textLinesY.line(highestY) + .orElseThrow(); + + final var line = + this.textLines.get(lineNumber); + + return highestY + line.height(); } @Override @@ -870,23 +815,13 @@ public Optional textSectionContainingLine( { Objects.requireNonNull(lineNumber, "lineNumber"); - final var y = this.textLineNumberToY.get(lineNumber); - if (y == null) { - return Optional.empty(); - } - final var line = this.textMeasuredLinesByY.get(y); + final var line = this.textLines.get(lineNumber); if (line == null) { return Optional.empty(); } return Optional.ofNullable(this.textSections.get(line.textOriginal())); } - @Override - public SortedMap linesByYCoordinate() - { - return new TreeMap<>(this.textMeasuredLinesByY); - } - @Override public Optional inspectAt( final PVector2I position) @@ -895,14 +830,20 @@ public Optional inspectAt( final var y = Math.max(0, position.y()); - final var entry = - this.textMeasuredLinesByY.floorEntry(Integer.valueOf(y)); + final var lineNumberOpt = + this.textLinesY.lineContainingY(y); - if (entry == null) { + if (lineNumberOpt.isEmpty()) { return Optional.empty(); } - return Optional.of(entry.getValue().inspectAtParentRelative(position)); + final var lineNumber = + lineNumberOpt.get(); + final var line = + Optional.ofNullable(this.textLines.get(lineNumber)) + .orElseThrow(); + + return Optional.of(line.inspectAtParentRelative(lineNumber, position)); } @Override @@ -912,12 +853,73 @@ public int minimumSizeYRequired() this.font.get(); var sum = fontNow.textHeight(); - for (final var value : this.textMeasuredLinesByY.values()) { - sum += value.pageLineBounds().sizeY(); + for (final var value : this.textLines.values()) { + sum += value.height(); } return sum; } + private enum SyEditOpRegenerate + implements SyEditOpType + { + SY_EDIT_OP_REGENERATE + } + + private sealed interface SyEditOpType + { + + } + + private record SyEditOpAppend( + SyTextID textID) + implements SyEditOpType + { + + } + + private static final class MappedIterator + implements Iterator + { + private final Iterator> baseIterator; + private final SyTextMultiLineModel model; + + MappedIterator( + final SyTextMultiLineModel inModel, + final Iterator> inBaseIterator) + { + this.model = + Objects.requireNonNull(inModel, "inModel"); + this.baseIterator = + Objects.requireNonNull(inBaseIterator, "baseIterator"); + } + + @Override + public boolean hasNext() + { + return this.baseIterator.hasNext(); + } + + @Override + public SyTextLinePositioned next() + { + final var nextEntry = + this.baseIterator.next(); + + final var lineNumber = + nextEntry.getKey(); + + final var line = + this.model.textLines.get(lineNumber); + + final var y = + this.model.textLinesY + .y(lineNumber) + .orElseThrow(); + + return new SyTextLinePositioned(y.intValue(), lineNumber, line); + } + } + private record SySelectionState( SyTextLocationType pivot, SyTextLocationType lowerInclusive, diff --git a/com.io7m.jsycamore.components.standard/src/main/java/com/io7m/jsycamore/components/standard/text/SyTextMultiLineView.java b/com.io7m.jsycamore.components.standard/src/main/java/com/io7m/jsycamore/components/standard/text/SyTextMultiLineView.java index 1660a006..f85bd9ba 100644 --- a/com.io7m.jsycamore.components.standard/src/main/java/com/io7m/jsycamore/components/standard/text/SyTextMultiLineView.java +++ b/com.io7m.jsycamore.components.standard/src/main/java/com/io7m/jsycamore/components/standard/text/SyTextMultiLineView.java @@ -35,15 +35,15 @@ import com.io7m.jsycamore.api.spaces.SySpaceParentRelativeType; import com.io7m.jsycamore.api.text.SyText; import com.io7m.jsycamore.api.text.SyTextLineMeasuredType; +import com.io7m.jsycamore.api.text.SyTextLinePositioned; import com.io7m.jsycamore.api.text.SyTextMultiLineModelType; import com.io7m.jsycamore.api.themes.SyThemeClassNameType; import com.io7m.jsycamore.components.standard.SyComponentAttributes; -import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.Objects; -import java.util.SortedMap; +import java.util.Optional; import static com.io7m.jsycamore.api.events.SyEventConsumed.EVENT_CONSUMED; import static com.io7m.jsycamore.api.events.SyEventConsumed.EVENT_NOT_CONSUMED; @@ -228,28 +228,40 @@ private void createModelIfRequired( } @Override - public SortedMap textsByYOffset() + public int minimumSizeYRequired( + final SyLayoutContextType layoutContext) + { + this.createModelIfRequired(layoutContext); + return this.textModel.minimumSizeYRequired(); + } + + @Override + public Optional textByYOffset( + final int y) { final var model = this.textModel; if (model == null) { - return Collections.emptySortedMap(); + return Optional.empty(); } else { - return model.linesByYCoordinate(); + return model.textByYOffset(y); } } @Override - public int minimumSizeYRequired( - final SyLayoutContextType layoutContext) + public AttributeType textSelectable() { - this.createModelIfRequired(layoutContext); - return this.textModel.minimumSizeYRequired(); + return this.textSelectable; } @Override - public AttributeType textSelectable() + public Iterable textLinesPositioned() { - return this.textSelectable; + final var model = this.textModel; + if (model == null) { + return List.of(); + } else { + return model.textLinesPositioned(); + } } @Override diff --git a/com.io7m.jsycamore.components.standard/src/main/java/com/io7m/jsycamore/components/standard/text/SyTextSingleLineModel.java b/com.io7m.jsycamore.components.standard/src/main/java/com/io7m/jsycamore/components/standard/text/SyTextSingleLineModel.java index 83dd57ce..98349edc 100644 --- a/com.io7m.jsycamore.components.standard/src/main/java/com/io7m/jsycamore/components/standard/text/SyTextSingleLineModel.java +++ b/com.io7m.jsycamore.components.standard/src/main/java/com/io7m/jsycamore/components/standard/text/SyTextSingleLineModel.java @@ -119,7 +119,6 @@ private void regenerate() fontNow.textLayout( SyTextID.first(), textNow, - SyTextLineNumber.first(), textWidth ); this.textMeasured = @@ -163,7 +162,7 @@ private List> buildRegions( yield sanitizedArea( xMinimum, xMaximum, - this.textMeasured.textBounds().sizeY() + this.textMeasured.height() ); } case TEXT_DIRECTION_RIGHT_TO_LEFT -> { @@ -175,7 +174,7 @@ yield sanitizedArea( yield sanitizedArea( xMinimum, xMaximum, - this.textMeasured.textBounds().sizeY() + this.textMeasured.height() ); } } @@ -263,7 +262,10 @@ public SyTextLocationType inspectAt( final PVector2I position) { Objects.requireNonNull(position, "position"); - return this.textMeasured.inspectAtParentRelative(position); + return this.textMeasured.inspectAtParentRelative( + SyTextLineNumber.first(), + position + ); } private record SySelectionState( diff --git a/com.io7m.jsycamore.components.standard/src/main/java/com/io7m/jsycamore/components/standard/text/SyTextView.java b/com.io7m.jsycamore.components.standard/src/main/java/com/io7m/jsycamore/components/standard/text/SyTextView.java index dd2f7a6d..3b13be52 100644 --- a/com.io7m.jsycamore.components.standard/src/main/java/com/io7m/jsycamore/components/standard/text/SyTextView.java +++ b/com.io7m.jsycamore.components.standard/src/main/java/com/io7m/jsycamore/components/standard/text/SyTextView.java @@ -266,8 +266,8 @@ public PAreaSizeI minimumSizeRequired( this.modelGetOrCreate(); final var textLine = this.textModel.lineMeasured(); - final var sizeX = textLine.textBounds().sizeX(); - final var sizeY = textLine.textBounds().sizeY(); + final var sizeX = textLine.textWidth(); + final var sizeY = textLine.height(); return PAreaSizeI.of(sizeX, sizeY); } diff --git a/com.io7m.jsycamore.documentation/src/main/resources/com/io7m/jsycamore/documentation/m-coordinates.xml b/com.io7m.jsycamore.documentation/src/main/resources/com/io7m/jsycamore/documentation/m-coordinates.xml index ef3f9aeb..698eb387 100644 --- a/com.io7m.jsycamore.documentation/src/main/resources/com/io7m/jsycamore/documentation/m-coordinates.xml +++ b/com.io7m.jsycamore.documentation/src/main/resources/com/io7m/jsycamore/documentation/m-coordinates.xml @@ -66,10 +66,11 @@ - + - The text space is a two-dimensional integer coordinate system used to address - text. + The text-aligned space is a two-dimensional integer coordinate system used to address + text. It takes into account the alignment applied to + text for left-to-right and right-to-left text. diff --git a/com.io7m.jsycamore.tests/src/main/java/com/io7m/jsycamore/tests/SyAWTFontTest.java b/com.io7m.jsycamore.tests/src/main/java/com/io7m/jsycamore/tests/SyAWTFontTest.java index 9dfa74ca..5207c729 100644 --- a/com.io7m.jsycamore.tests/src/main/java/com/io7m/jsycamore/tests/SyAWTFontTest.java +++ b/com.io7m.jsycamore.tests/src/main/java/com/io7m/jsycamore/tests/SyAWTFontTest.java @@ -23,7 +23,6 @@ import com.io7m.jsycamore.api.text.SyText; import com.io7m.jsycamore.api.text.SyTextID; import com.io7m.jsycamore.api.text.SyTextLineMeasuredType; -import com.io7m.jsycamore.api.text.SyTextLineNumber; import com.io7m.jsycamore.awt.internal.SyAWTFont; import com.io7m.jsycamore.awt.internal.SyAWTFontDirectoryService; import org.junit.jupiter.api.BeforeEach; @@ -67,7 +66,6 @@ public void testLayout() this.font.textLayout( SyTextID.first(), text, - SyTextLineNumber.first(), 320 ) ); diff --git a/com.io7m.jsycamore.tests/src/main/java/com/io7m/jsycamore/tests/SyTextMultiLineViewTest.java b/com.io7m.jsycamore.tests/src/main/java/com/io7m/jsycamore/tests/SyTextMultiLineViewTest.java index 772a9c49..3bad1b87 100644 --- a/com.io7m.jsycamore.tests/src/main/java/com/io7m/jsycamore/tests/SyTextMultiLineViewTest.java +++ b/com.io7m.jsycamore.tests/src/main/java/com/io7m/jsycamore/tests/SyTextMultiLineViewTest.java @@ -197,8 +197,8 @@ public void testTextDeferred() c.textSectionAppend(SyText.text("Hello!")); assertEquals( - Collections.emptySortedMap(), - c.textsByYOffset() + Optional.empty(), + c.textByYOffset(0) ); this.windowContentArea().childAdd(c); @@ -206,7 +206,7 @@ public void testTextDeferred() assertEquals( SyText.text("Hello!"), - c.textsByYOffset().get(0).textAsWrapped() + c.textByYOffset(0).orElseThrow().textAsWrapped() ); c.textSectionAppend(SyText.text("Goodbye!")); @@ -214,11 +214,11 @@ public void testTextDeferred() assertEquals( SyText.text("Hello!"), - c.textsByYOffset().get(0).textAsWrapped() + c.textByYOffset(0).orElseThrow().textAsWrapped() ); assertEquals( SyText.text("Goodbye!"), - c.textsByYOffset().get(14).textAsWrapped() + c.textByYOffset(14).orElseThrow().textAsWrapped() ); } diff --git a/com.io7m.jsycamore.theme.primal/src/main/java/com/io7m/jsycamore/theme/primal/internal/SyPrimalTextMultilineView.java b/com.io7m.jsycamore.theme.primal/src/main/java/com/io7m/jsycamore/theme/primal/internal/SyPrimalTextMultilineView.java index b70b216e..2ea1f1d4 100644 --- a/com.io7m.jsycamore.theme.primal/src/main/java/com/io7m/jsycamore/theme/primal/internal/SyPrimalTextMultilineView.java +++ b/com.io7m.jsycamore.theme.primal/src/main/java/com/io7m/jsycamore/theme/primal/internal/SyPrimalTextMultilineView.java @@ -79,13 +79,11 @@ public SyRenderNodeType render( final var font = this.font(context, component); - final var texts = - textView.textsByYOffset(); final var textViewSize = textView.size().get(); final var nodes = - new ArrayList(texts.size()); + new ArrayList(); /* * Render a background behind all text selection regions. @@ -122,21 +120,14 @@ public SyRenderNodeType render( * Create a text node for each line of text. */ - for (final var entry : texts.entrySet()) { - final var yOffset = - entry.getKey(); + for (final var linePositioned : textView.textLinesPositioned()) { final var line = - entry.getValue(); - final var lineSize = - line.textBounds(); + linePositioned.textLine(); final PVector2I position = - PVector2I.of(0, yOffset.intValue()); + PVector2I.of(0, linePositioned.y()); final PAreaSizeI size = - PAreaSizeI.of( - textViewSize.sizeX(), - lineSize.sizeY() - ); + PAreaSizeI.of(textViewSize.sizeX(), line.height()); nodes.add( new SyRenderNodeText(