Skip to content

Commit 1b26b66

Browse files
author
Andy Goryachev
committed
8351878: RichTextArea: copy/paste issues
Reviewed-by: lkostyra, zelmidaoui
1 parent f31d00d commit 1b26b66

File tree

7 files changed

+611
-27
lines changed

7 files changed

+611
-27
lines changed

modules/jfx.incubator.richtext/src/main/java/com/sun/jfx/incubator/scene/control/richtext/HtmlStyledOutput.java

Lines changed: 29 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -73,16 +73,21 @@ public void consume(StyledSegment seg) throws IOException {
7373
break;
7474
case TEXT:
7575
StyleAttributeMap a = seg.getStyleAttributeMap(resolver);
76-
boolean div = ((a != null) && (!a.isEmpty()));
77-
if (div) {
76+
String styles;
77+
if ((a != null) && (!a.isEmpty())) {
78+
styles = extractStyles(a);
79+
} else {
80+
styles = null;
81+
}
82+
if (styles != null) {
7883
wr.write("<span style='");
79-
writeAttributes(a);
84+
wr.write(styles);
8085
wr.write("'>");
8186
}
8287
String text = seg.getText();
8388
String encoded = encode(text);
8489
wr.write(encoded);
85-
if (div) {
90+
if (styles != null) {
8691
wr.write("</span>");
8792
}
8893
break;
@@ -93,28 +98,35 @@ public void consume(StyledSegment seg) throws IOException {
9398
}
9499
}
95100

96-
private void writeAttributes(StyleAttributeMap attrs) throws IOException {
101+
private String extractStyles(StyleAttributeMap attrs) {
102+
StringBuilder sb = new StringBuilder();
97103
boolean sp = false;
98104
for (StyleAttribute a : attrs.getAttributes()) {
99105
Object v = attrs.get(a);
100106
if (v != null) {
101107
Key k = createKey(attrs, a, v);
102108
if (k != null) {
103109
if (sp) {
104-
wr.write(' ');
110+
sb.append(' ');
105111
} else {
106112
sp = true;
107113
}
108114

109115
Val val = styles.get(k);
110-
if (inlineStyles) {
111-
wr.write(val.css);
112-
} else {
113-
wr.write(val.name);
116+
if (val != null) {
117+
if (inlineStyles) {
118+
sb.append(val.css);
119+
} else {
120+
sb.append(val.name);
121+
}
114122
}
115123
}
116124
}
117125
}
126+
if (sb.length() == 0) {
127+
return null;
128+
}
129+
return sb.toString();
118130
}
119131

120132
/**
@@ -310,21 +322,21 @@ public void close() throws IOException {
310322

311323
private static String createCss(StyleAttribute a, Object v) {
312324
if (a == StyleAttributeMap.BOLD) {
313-
return "font-weight: bold;";
325+
return Boolean.TRUE.equals(v) ? "font-weight: bold;" : null;
314326
} else if (a == StyleAttributeMap.FONT_FAMILY) {
315327
return "font-family: \"" + encodeFontFamily(v.toString()) + "\";";
316328
} else if (a == StyleAttributeMap.FONT_SIZE) {
317-
return "font-size: " + v + "pt;";
329+
return "font-size: " + v + "px;";
318330
} else if (a == StyleAttributeMap.ITALIC) {
319-
return "font-style: italic;";
320-
} else if (a == StyleAttributeMap.STRIKE_THROUGH) {
331+
return Boolean.TRUE.equals(v) ? "font-style: italic;" : null;
332+
} else if (a == StyleAttributeMap.STRIKE_THROUGH && Boolean.TRUE.equals(v)) {
321333
return "text-decoration: line-through;";
322334
} else if (a == StyleAttributeMap.TEXT_COLOR) {
323335
return "color: " + RichUtils.toWebColor((Color)v) + ";";
324336
} else if (a == StyleAttributeMap.UNDERLINE) {
325-
return "text-decoration: underline;";
337+
return Boolean.TRUE.equals(v) ? "text-decoration: underline;" : null;
326338
} else if (a == SS_AND_UNDERLINE) {
327-
return "text-decoration: line-through underline;";
339+
return Boolean.TRUE.equals(v) ? "text-decoration: line-through underline;" : null;
328340
} else {
329341
return null;
330342
}

modules/jfx.incubator.richtext/src/main/java/com/sun/jfx/incubator/scene/control/richtext/RichTextAreaBehavior.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1230,6 +1230,7 @@ protected void copyWithCut(boolean cut) {
12301230
}
12311231
}
12321232
} catch(Exception | OutOfMemoryError e) {
1233+
e.printStackTrace();
12331234
control.errorFeedback();
12341235
}
12351236
}

modules/jfx.incubator.richtext/src/main/java/com/sun/jfx/incubator/scene/control/richtext/rtf/AttrSet.java

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -41,6 +41,7 @@ public class AttrSet {
4141
private AttrSet parent;
4242

4343
public AttrSet(AttrSet a) {
44+
addAttributes(a);
4445
}
4546

4647
public AttrSet() {
@@ -84,13 +85,19 @@ public void setResolveParent(AttrSet parent) {
8485
}
8586

8687
public StyleAttributeMap getStyleAttributeMap() {
87-
return StyleAttributeMap.builder().
88+
StyleAttributeMap.Builder b = StyleAttributeMap.builder();
89+
b.
8890
setBold(getBoolean(StyleAttributeMap.BOLD)).
8991
setFontFamily(getString(StyleAttributeMap.FONT_FAMILY)).
9092
setItalic(getBoolean(StyleAttributeMap.ITALIC)).
93+
setStrikeThrough(getBoolean(StyleAttributeMap.STRIKE_THROUGH)).
9194
setTextColor(getColor(StyleAttributeMap.TEXT_COLOR)).
92-
setUnderline(getBoolean(StyleAttributeMap.UNDERLINE)).
93-
build();
95+
setUnderline(getBoolean(StyleAttributeMap.UNDERLINE));
96+
Double d = getDouble(StyleAttributeMap.FONT_SIZE);
97+
if (d != null) {
98+
b.setFontSize(d);
99+
}
100+
return b.build();
94101
}
95102

96103
private boolean getBoolean(Object attr) {
@@ -113,6 +120,14 @@ private Color getColor(Object attr) {
113120
return null;
114121
}
115122

123+
private Double getDouble(Object attr) {
124+
Object v = attrs.get(attr);
125+
if (v instanceof Number n) {
126+
return n.doubleValue();
127+
}
128+
return null;
129+
}
130+
116131
public void setItalic(boolean on) {
117132
attrs.put(StyleAttributeMap.ITALIC, on);
118133
}

modules/jfx.incubator.richtext/src/main/java/com/sun/jfx/incubator/scene/control/richtext/rtf/RTFParser.java

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 1997, 2024, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -27,6 +27,10 @@
2727

2828
import java.io.ByteArrayOutputStream;
2929
import java.io.IOException;
30+
import java.nio.ByteBuffer;
31+
import java.nio.CharBuffer;
32+
import java.nio.charset.CharsetDecoder;
33+
import java.nio.charset.CoderResult;
3034

3135
/**
3236
* <b>RTFParser</b> is a subclass of <b>AbstractFilter</b> which understands basic RTF syntax
@@ -60,6 +64,12 @@ abstract class RTFParser extends AbstractFilter {
6064
private final int S_aftertickc = 5; // after reading \'x
6165
private final int S_inblob = 6; // in a \bin blob
6266

67+
// For fcharset control word
68+
protected CharsetDecoder decoder;
69+
protected final ByteBuffer decoderBB = ByteBuffer.wrap(new byte[2]);
70+
private final char[] decoderCA = new char[1];
71+
private final CharBuffer decoderCB = CharBuffer.wrap(decoderCA);
72+
6373
/** Implemented by subclasses to interpret a parameter-less RTF keyword.
6474
* The keyword is passed without the leading '/' or any delimiting
6575
* whitespace. */
@@ -98,13 +108,19 @@ public void handleText(char ch) {
98108
rtfSpecialsTable['\\'] = true;
99109
}
100110

111+
// Defined for replacement character
112+
private static final char REPLACEMENT_CHAR = '\uFFFD';
113+
101114
public RTFParser() {
102115
currentCharacters = new StringBuffer();
103116
state = S_text;
104117
pendingKeyword = null;
105118
level = 0;
106119

107120
specialsTable = rtfSpecialsTable;
121+
// Initialize byte buffer for CharsetDecoder
122+
decoderBB.clear();
123+
decoderBB.limit(1);
108124
}
109125

110126
@Override
@@ -169,6 +185,9 @@ public void write(char ch) throws IOException {
169185
}
170186
state = S_backslashed;
171187
} else {
188+
// SBCS: ASCII character
189+
// DBCS: Non lead byte
190+
ch = decode(ch);
172191
currentCharacters.append(ch);
173192
}
174193
break;
@@ -286,7 +305,8 @@ public void write(char ch) throws IOException {
286305
state = S_text;
287306
if (Character.digit(ch, 16) != -1) {
288307
pendingCharacter = pendingCharacter * 16 + Character.digit(ch, 16);
289-
ch = translationTable[pendingCharacter];
308+
// Use translationTable if decoder is not defined
309+
ch = decoder == null ? translationTable[pendingCharacter] : decode((char)pendingCharacter);
290310
if (ch != 0) {
291311
handleText(ch);
292312
}
@@ -343,4 +363,33 @@ public void close() throws IOException {
343363

344364
super.close();
345365
}
366+
367+
private char decode(char ch) {
368+
if (decoder == null) return ch;
369+
decoderBB.put((byte) ch);
370+
decoderBB.rewind();
371+
decoderCB.clear();
372+
CoderResult cr = decoder.decode(decoderBB, decoderCB, false);
373+
if (cr.isUnderflow()) {
374+
if (decoderCB.position() == 1) {
375+
// Converted to Unicode (including replacement character)
376+
decoder.reset();
377+
decoderBB.clear();
378+
decoderBB.limit(1);
379+
return decoderCA[0];
380+
} else {
381+
// Detected lead byte
382+
decoder.reset();
383+
decoderBB.limit(2);
384+
decoderBB.position(1);
385+
return 0; // Skip write operation if return value is 0
386+
}
387+
} else {
388+
// Fallback, should not be called
389+
decoder.reset();
390+
decoderBB.clear();
391+
decoderBB.limit(1);
392+
return REPLACEMENT_CHAR;
393+
}
394+
}
346395
}

0 commit comments

Comments
 (0)