From 36aa590bb6028a04df4019f6b550be481f4ff845 Mon Sep 17 00:00:00 2001 From: satoshiokita Date: Mon, 8 Feb 2016 09:47:07 +0900 Subject: [PATCH] add to fiexed the composed text is shown doubly. change to cjk support from japanese only remove code depend on textManager. clean comment outed old code. rewirte getTextLocation method to simple logic --- .../app/syntax/im/CompositionTextManager.java | 32 ++- .../app/syntax/im/CompositionTextPainter.java | 99 +++++--- .../app/syntax/im/InputMethodSupport.java | 225 +++++++++++++----- 3 files changed, 264 insertions(+), 92 deletions(-) diff --git a/app/src/processing/app/syntax/im/CompositionTextManager.java b/app/src/processing/app/syntax/im/CompositionTextManager.java index 12a9ad341..04fbe5f74 100644 --- a/app/src/processing/app/syntax/im/CompositionTextManager.java +++ b/app/src/processing/app/syntax/im/CompositionTextManager.java @@ -5,6 +5,7 @@ import java.awt.FontMetrics; import java.awt.Graphics2D; import java.awt.Point; import java.awt.Rectangle; +import java.awt.RenderingHints; import java.awt.font.FontRenderContext; import java.awt.font.TextAttribute; import java.awt.font.TextLayout; @@ -14,6 +15,9 @@ import java.text.CharacterIterator; import javax.swing.text.BadLocationException; +import processing.app.Base; +import processing.app.Messages; +import processing.app.Preferences; import processing.app.syntax.JEditTextArea; import processing.app.syntax.TextAreaPainter; @@ -152,14 +156,28 @@ public class CompositionTextManager { e.printStackTrace(); } } - - private TextLayout getTextLayout(AttributedCharacterIterator text, int committed_count) { - AttributedString composed = new AttributedString(text, committed_count, text.getEndIndex()); - Font font = textArea.getPainter().getFont(); - FontRenderContext context = ((Graphics2D) (textArea.getPainter().getGraphics())).getFontRenderContext(); + + private TextLayout getTextLayout(AttributedCharacterIterator text, int committedCount) { + boolean antialias = Preferences.getBoolean("editor.smooth"); + TextAreaPainter painter = textArea.getPainter(); + + // create attributed string with font info. + AttributedString composed = new AttributedString(text, committedCount, text.getEndIndex()); + Font font = painter.getFontMetrics().getFont(); composed.addAttribute(TextAttribute.FONT, font); - TextLayout layout = new TextLayout(composed.getIterator(), context); - return layout; + + // set hint of antialiasing to render target. + Graphics2D g2d = (Graphics2D)painter.getGraphics(); + g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, + antialias ? + RenderingHints.VALUE_TEXT_ANTIALIAS_ON : + RenderingHints.VALUE_TEXT_ANTIALIAS_OFF); + FontRenderContext frc = g2d.getFontRenderContext(); + if (Base.DEBUG) { + Messages.log("debug: FontRenderContext is Antialiased = " + frc.getAntiAliasingHint()); + } + + return new TextLayout(composed.getIterator(), frc); } private Point getCaretLocation() { diff --git a/app/src/processing/app/syntax/im/CompositionTextPainter.java b/app/src/processing/app/syntax/im/CompositionTextPainter.java index c12d7ed30..bb0e43e98 100644 --- a/app/src/processing/app/syntax/im/CompositionTextPainter.java +++ b/app/src/processing/app/syntax/im/CompositionTextPainter.java @@ -1,12 +1,15 @@ package processing.app.syntax.im; import java.awt.Color; -import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Point; +import java.awt.Rectangle; +import java.awt.font.TextHitInfo; import java.awt.font.TextLayout; +import processing.app.Base; +import processing.app.Messages; import processing.app.syntax.JEditTextArea; /** @@ -24,8 +27,9 @@ public class CompositionTextPainter { private TextLayout composedTextLayout; private int composedBeginCaretPosition = 0; private JEditTextArea textArea; + private boolean caretColorFlag; + private TextHitInfo caret; - /** * Constructor for painter. * @param textArea textarea used by PDE. @@ -83,45 +87,76 @@ public class CompositionTextPainter { * * This draw method is proceeded with the following steps. * 1. Original TextAreaPainter draws characters. - * 2. This refillComposedArea method erase previous paint characters by textarea's background color. - * The refill area is only square that width and height defined by characters with input method. - * 3. CompositionTextPainter.draw method paints composed text. It was actually drawn by TextLayout. + * 2. CompositionTextPainter.draw method paints composed text. It was actually drawn by TextLayout. * * @param gfx set TextAreaPainter's Graphics object. * @param fillBackGroundColor set textarea's background. */ +// public void draw(Graphics gfx, Color fillBackGroundColor) { +// assert(composedTextLayout != null); +// Point composedLoc = getCaretLocation(); +// //refillComposedArea(fillBackGroundColor, composedLoc.x, composedLoc.y); +// composedTextLayout.draw((Graphics2D) gfx, composedLoc.x, composedLoc.y); +// } public void draw(Graphics gfx, Color fillBackGroundColor) { - assert(composedTextLayout != null); - Point composedLoc = getCaretLocation(); - refillComposedArea(fillBackGroundColor, composedLoc.x, composedLoc.y); - composedTextLayout.draw((Graphics2D) gfx, composedLoc.x, composedLoc.y); + //assert(composedTextLayout != null); + if (composedTextLayout != null) { + Point composedLoc = getCaretLocation(); + //refillComposedArea(fillBackGroundColor, composedLoc.x, composedLoc.y); + composedTextLayout.draw((Graphics2D) gfx, composedLoc.x, composedLoc.y); + + // draw caret. + if (this.caret != null) { + int caretLocation = Math.round(composedTextLayout.getCaretInfo(caret)[0]); + Rectangle rect = new Rectangle(composedLoc.x + caretLocation, + composedLoc.y - (int)composedTextLayout.getAscent(), + 1, + (int)composedTextLayout.getAscent() + (int)composedTextLayout.getDescent()); + // blink caret. + if (Base.DEBUG) { + Messages.log(rect.toString()); + } + Color c = gfx.getColor(); // save + if (caretColorFlag) { + caretColorFlag = false; + gfx.setColor(Color.WHITE); + } else { + caretColorFlag = true; + gfx.setColor(Color.BLACK); + } + gfx.fillRect(rect.x, rect.y, rect.width, rect.height); + gfx.setColor(c); // restore + } + } } - - /** - * Fill color to erase characters drawn by original TextAreaPainter. - * - * @param fillColor fill color to erase characters drawn by original TextAreaPainter method. - * @param x x-coordinate where to fill. - * @param y y-coordinate where to fill. - */ - private void refillComposedArea(Color fillColor, int x, int y) { - Graphics gfx = textArea.getPainter().getGraphics(); - gfx.setColor(fillColor); - FontMetrics fm = textArea.getPainter().getFontMetrics(); - int newY = y - (fm.getHeight() - CompositionTextManager.COMPOSING_UNDERBAR_HEIGHT); - int paintHeight = fm.getHeight(); - int paintWidth = (int) composedTextLayout.getBounds().getWidth(); - gfx.fillRect(x, newY, paintWidth, paintHeight); - } +// /** +// * Fill color to erase characters drawn by original TextAreaPainter. +// * +// * @param fillColor fill color to erase characters drawn by original TextAreaPainter method. +// * @param x x-coordinate where to fill. +// * @param y y-coordinate where to fill. +// */ +// private void refillComposedArea(Color fillColor, int x, int y) { +// Graphics gfx = textArea.getPainter().getGraphics(); +// gfx.setColor(fillColor); +// FontMetrics fm = textArea.getPainter().getFontMetrics(); +// int newY = y - (fm.getHeight() - CompositionTextManager.COMPOSING_UNDERBAR_HEIGHT); +// int paintHeight = fm.getHeight(); +// int paintWidth = (int) composedTextLayout.getBounds().getWidth(); +// gfx.fillRect(x, newY, paintWidth, paintHeight); +// } private Point getCaretLocation() { - FontMetrics fm = textArea.getPainter().getFontMetrics(); - int offsetY = fm.getHeight() - CompositionTextManager.COMPOSING_UNDERBAR_HEIGHT; - int lineIndex = textArea.getCaretLine(); - int offsetX = composedBeginCaretPosition - textArea.getLineStartOffset(lineIndex); - return new Point(textArea.offsetToX(lineIndex, offsetX), - (lineIndex - textArea.getFirstLine()) * fm.getHeight() + offsetY); + int line = textArea.getCaretLine(); + int offsetX = composedBeginCaretPosition - textArea.getLineStartOffset(line); + // '+1' mean textArea.lineToY(line) + textArea.getPainter().getFontMetrics().getHeight(). + // TextLayout#draw method need at least one height of font. + return new Point(textArea.offsetToX(line, offsetX), textArea.lineToY(line + 1)); + } + + public void setCaret(TextHitInfo caret) { + this.caret = caret; } } diff --git a/app/src/processing/app/syntax/im/InputMethodSupport.java b/app/src/processing/app/syntax/im/InputMethodSupport.java index 461be3d16..921cfc83b 100644 --- a/app/src/processing/app/syntax/im/InputMethodSupport.java +++ b/app/src/processing/app/syntax/im/InputMethodSupport.java @@ -1,120 +1,239 @@ package processing.app.syntax.im; +import java.awt.Color; +import java.awt.Font; +import java.awt.Graphics2D; +import java.awt.Point; import java.awt.Rectangle; +import java.awt.RenderingHints; import java.awt.event.InputMethodEvent; import java.awt.event.InputMethodListener; +import java.awt.font.FontRenderContext; +import java.awt.font.TextAttribute; import java.awt.font.TextHitInfo; +import java.awt.font.TextLayout; import java.awt.im.InputMethodRequests; import java.text.AttributedCharacterIterator; +import java.text.AttributedCharacterIterator.Attribute; +import javax.swing.text.BadLocationException; + +import java.text.AttributedString; + +import processing.app.Base; +import processing.app.Messages; +import processing.app.Preferences; import processing.app.syntax.JEditTextArea; +import processing.app.syntax.TextAreaPainter; /** - * Support in-line Japanese input for PDE. (Maybe Chinese, Korean and more) - * This class is implemented by Java Input Method Framework and handles - * If you would like to know more about Java Input Method Framework, - * Please see http://java.sun.com/j2se/1.5.0/docs/guide/imf/ - * - * This class is implemented to fix Bug #854. - * http://dev.processing.org/bugs/show_bug.cgi?id=854 + * on-the-spot style input support for CJK.(Chinese, Japanese, Korean). + * This class is implemented to fix Bug #854 from 2010-02-16. + * + * @see Bug 854 : implement input method support for Japanese (and other languages) + * @see Bug 1531 : Can't input full-width space when Japanese IME is on. + * @see http://docs.oracle.com/javase/8/docs/technotes/guides/imf/index.html + * @see http://docs.oracle.com/javase/tutorial/2d/text/index.html * * @author Takashi Maekawa (takachin@generative.info) + * @author Satoshi Okita */ public class InputMethodSupport implements InputMethodRequests, InputMethodListener { + private static final Attribute[] CUSTOM_IM_ATTRIBUTES = { + TextAttribute.INPUT_METHOD_HIGHLIGHT, + }; + private int committed_count = 0; - private CompositionTextManager textManager; + private TextHitInfo caret; + private JEditTextArea textArea; + private AttributedCharacterIterator composedText; + private AttributedString composedTextString; public InputMethodSupport(JEditTextArea textArea) { - textManager = new CompositionTextManager(textArea); - textArea.enableInputMethods(true); - textArea.addInputMethodListener(this); + this.textArea = textArea; + this.textArea.enableInputMethods(true); + this.textArea.addInputMethodListener(this); } + ///////////////////////////////////////////////////////////////////////////// + // InputMethodRequest + ///////////////////////////////////////////////////////////////////////////// + @Override public Rectangle getTextLocation(TextHitInfo offset) { - return textManager.getTextLocation(); + if (Base.DEBUG) { + Messages.log("#Called getTextLocation:" + offset); + } + int line = textArea.getCaretLine(); + int offsetX = textArea.getCaretPosition() - textArea.getLineStartOffset(line); + // '+1' mean textArea.lineToY(line) + textArea.getPainter().getFontMetrics().getHeight(). + // TextLayout#draw method need at least one height of font. + Rectangle rectangle = new Rectangle(textArea.offsetToX(line, offsetX), textArea.lineToY(line + 1), 0, 0); + + Point location = textArea.getPainter().getLocationOnScreen(); + rectangle.translate(location.x, location.y); + + return rectangle; } + + @Override public TextHitInfo getLocationOffset(int x, int y) { return null; } + @Override public int getInsertPositionOffset() { - return textManager.getInsertPositionOffset(); + return textArea.getCaretPosition() * -1; } - + + @Override public AttributedCharacterIterator getCommittedText(int beginIndex, int endIndex, AttributedCharacterIterator.Attribute[] attributes) { - return textManager.getCommittedText(beginIndex, endIndex); + int length = endIndex - beginIndex; + String textAreaString = textArea.getText(beginIndex, length); + return new AttributedString(textAreaString).getIterator(); } + @Override public int getCommittedTextLength() { return committed_count; } + @Override public AttributedCharacterIterator cancelLatestCommittedText( AttributedCharacterIterator.Attribute[] attributes) { return null; } + @Override public AttributedCharacterIterator getSelectedText( AttributedCharacterIterator.Attribute[] attributes) { return null; } + ///////////////////////////////////////////////////////////////////////////// + // InputMethodListener + ///////////////////////////////////////////////////////////////////////////// /** * Handles events from InputMethod. - * This method judges whether beginning of input or - * progress of input or end and call related method. * * @param event event from Input Method. */ - public void inputMethodTextChanged(InputMethodEvent event) { - AttributedCharacterIterator text = event.getText(); + @Override + public void inputMethodTextChanged(InputMethodEvent event) { + composedText = null; + if (Base.DEBUG) { + StringBuilder sb = new StringBuilder(); + sb.append("#Called inputMethodTextChanged"); + sb.append("\t ID: " + event.getID()); + sb.append("\t timestamp: " + new java.util.Date(event.getWhen())); + sb.append("\t parmString: " + event.paramString()); + Messages.log(sb.toString()); + } + + AttributedCharacterIterator text = event.getText(); // text = composedText + commitedText committed_count = event.getCommittedCharacterCount(); - if(isFullWidthSpaceInput(text)){ - textManager.insertFullWidthSpace(); - caretPositionChanged(event); - return; + + + // The caret for Input Method. + // if you type a character by a input method, original caret become off. + // a JEditTextArea is not implemented by the AttributedStirng and TextLayout. + // so JEditTextArea Caret On-off logic. + // + // japanese : if the enter key pressed, event.getText is null. + // japanese : if first space key pressed, event.getText is null. + // chinese(pinin) : if a space key pressed, event.getText is null. + // taiwan(bopomofo): ? + // korean : ? + textArea.setCaretVisible(false); + // Korean Input Method + if (text != null && text.getEndIndex() - (text.getBeginIndex() + committed_count) <= 0) { + textArea.setCaretVisible(true); } - if(isBeginInputProcess(text, textManager)){ - textManager.beginCompositionText(text, committed_count); - caretPositionChanged(event); - return; + // Japanese Input Method + if (text == null) { + textArea.setCaretVisible(true); } - if (isInputProcess(text)){ - textManager.processCompositionText(text, committed_count); - caretPositionChanged(event); - return; + + char c; + if (text != null) { + int toCopy = committed_count; + c = text.first(); + while (toCopy-- > 0) { + if (Base.DEBUG) { + Messages.log("INSERT:'" + c + "'"); + } + this.insertCharacter(c); + c = text.next(); + } + + CompositionTextPainter compositionPainter = textArea.getPainter().getCompositionTextpainter(); + if (Base.DEBUG) { + Messages.log(" textArea.getCaretPosition() + committed_count: " + (textArea.getCaretPosition() + committed_count)); + } + compositionPainter.setComposedTextLayout(getTextLayout(text, committed_count), textArea.getCaretPosition() + committed_count); + compositionPainter.setCaret(event.getCaret()); + } else { + // hide input method. + CompositionTextPainter compositionPainter = textArea.getPainter().getCompositionTextpainter(); + compositionPainter.setComposedTextLayout(null, 0); + compositionPainter.setCaret(null); } - textManager.endCompositionText(text, committed_count); - caretPositionChanged(event); + caret = event.getCaret(); + event.consume(); + textArea.repaint(); } - private boolean isFullWidthSpaceInput(AttributedCharacterIterator text){ - if(text == null) - return false; - if(textManager.getIsInputProcess()) - return false; - return (String.valueOf(text.first()).equals("\u3000")); - } - - private boolean isBeginInputProcess(AttributedCharacterIterator text, CompositionTextManager textManager){ - if(text == null) - return false; - if(textManager.getIsInputProcess()) - return false; - return (isInputProcess(text)); - } - - private boolean isInputProcess(AttributedCharacterIterator text){ - if(text == null) - return false; - return (text.getEndIndex() - (text.getBeginIndex() + committed_count) > 0); + private TextLayout getTextLayout(AttributedCharacterIterator text, int committedCount) { + boolean antialias = Preferences.getBoolean("editor.smooth"); + TextAreaPainter painter = textArea.getPainter(); + + // create attributed string with font info. + //if (text.getEndIndex() - (text.getBeginIndex() + committedCharacterCount) > 0) { + if (text.getEndIndex() - (text.getBeginIndex() + committedCount) > 0) { + composedTextString = new AttributedString(text, committedCount, text.getEndIndex(), CUSTOM_IM_ATTRIBUTES); + Font font = painter.getFontMetrics().getFont(); + composedTextString.addAttribute(TextAttribute.FONT, font); + composedTextString.addAttribute(TextAttribute.BACKGROUND, Color.WHITE); + composedText = composedTextString.getIterator(); + } else { + composedTextString = new AttributedString(""); + return null; + } + + // set hint of antialiasing to render target. + Graphics2D g2d = (Graphics2D)painter.getGraphics(); + g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, + antialias ? + RenderingHints.VALUE_TEXT_ANTIALIAS_ON : + RenderingHints.VALUE_TEXT_ANTIALIAS_OFF); + FontRenderContext frc = g2d.getFontRenderContext(); + if (Base.DEBUG) { + Messages.log("debug: FontRenderContext is Antialiased = " + frc.getAntiAliasingHint()); + } + + return new TextLayout(composedTextString.getIterator(), frc); } + @Override public void caretPositionChanged(InputMethodEvent event) { + caret = event.getCaret(); event.consume(); } + + private void insertCharacter(char c) { + if (Base.DEBUG) { + Messages.log("debug: insertCharacter(char c) textArea.getCaretPosition()=" + textArea.getCaretPosition()); + } + try { + textArea.getDocument().insertString(textArea.getCaretPosition(), Character.toString(c), null); + if (Base.DEBUG) { + Messages.log("debug: \t after:insertCharacter(char c) textArea.getCaretPosition()=" + textArea.getCaretPosition()); + } + } catch (BadLocationException e) { + e.printStackTrace(); + } + } }