From 54b6cfa9a9e5b861a9930af873580d6dc20f773c Mon Sep 17 00:00:00 2001 From: The Android Open Source Project Date: Tue, 21 Oct 2008 07:00:00 -0700 Subject: Initial Contribution --- awt/org/apache/harmony/awt/ChoiceStyle.java | 33 + awt/org/apache/harmony/awt/ClipRegion.java | 84 ++ awt/org/apache/harmony/awt/ComponentInternals.java | 212 ++++ awt/org/apache/harmony/awt/ContextStorage.java | 154 +++ awt/org/apache/harmony/awt/ContextThreadGroup.java | 34 + awt/org/apache/harmony/awt/ListenerList.java | 194 ++++ awt/org/apache/harmony/awt/ReadOnlyIterator.java | 53 + .../harmony/awt/gl/AwtImageBackdoorAccessor.java | 65 ++ .../apache/harmony/awt/gl/CommonGraphics2D.java | 1132 ++++++++++++++++++++ .../harmony/awt/gl/CommonGraphics2DFactory.java | 78 ++ .../harmony/awt/gl/CommonGraphicsEnvironment.java | 67 ++ awt/org/apache/harmony/awt/gl/Crossing.java | 889 +++++++++++++++ awt/org/apache/harmony/awt/gl/GLVolatileImage.java | 30 + .../apache/harmony/awt/gl/ICompositeContext.java | 90 ++ awt/org/apache/harmony/awt/gl/ImageSurface.java | 323 ++++++ awt/org/apache/harmony/awt/gl/MultiRectArea.java | 836 +++++++++++++++ awt/org/apache/harmony/awt/gl/MultiRectAreaOp.java | 837 +++++++++++++++ awt/org/apache/harmony/awt/gl/Surface.java | 309 ++++++ awt/org/apache/harmony/awt/gl/TextRenderer.java | 59 + awt/org/apache/harmony/awt/gl/XORComposite.java | 48 + .../harmony/awt/gl/color/ColorConverter.java | 257 +++++ .../apache/harmony/awt/gl/color/ColorScaler.java | 355 ++++++ .../harmony/awt/gl/color/ICC_ProfileHelper.java | 82 ++ .../apache/harmony/awt/gl/color/ICC_Transform.java | 156 +++ .../harmony/awt/gl/color/LUTColorConverter.java | 148 +++ awt/org/apache/harmony/awt/gl/color/NativeCMM.java | 92 ++ .../harmony/awt/gl/color/NativeImageFormat.java | 642 +++++++++++ .../apache/harmony/awt/gl/font/AndroidFont.java | 254 +++++ .../harmony/awt/gl/font/AndroidFontManager.java | 277 +++++ .../harmony/awt/gl/font/AndroidFontProperty.java | 81 ++ .../harmony/awt/gl/font/AndroidGlyphVector.java | 219 ++++ .../harmony/awt/gl/font/AndroidLineMetrics.java | 120 +++ .../apache/harmony/awt/gl/font/BasicMetrics.java | 134 +++ .../apache/harmony/awt/gl/font/CaretManager.java | 530 +++++++++ .../harmony/awt/gl/font/CommonGlyphVector.java | 954 +++++++++++++++++ .../apache/harmony/awt/gl/font/CompositeFont.java | 486 +++++++++ .../harmony/awt/gl/font/FontExtraMetrics.java | 145 +++ awt/org/apache/harmony/awt/gl/font/FontFinder.java | 121 +++ .../apache/harmony/awt/gl/font/FontManager.java | 819 ++++++++++++++ .../harmony/awt/gl/font/FontMetricsImpl.java | 282 +++++ .../apache/harmony/awt/gl/font/FontPeerImpl.java | 499 +++++++++ .../apache/harmony/awt/gl/font/FontProperty.java | 106 ++ awt/org/apache/harmony/awt/gl/font/Glyph.java | 236 ++++ .../harmony/awt/gl/font/LineMetricsImpl.java | 412 +++++++ .../apache/harmony/awt/gl/font/TextDecorator.java | 433 ++++++++ .../harmony/awt/gl/font/TextMetricsCalculator.java | 209 ++++ .../apache/harmony/awt/gl/font/TextRunBreaker.java | 861 +++++++++++++++ .../apache/harmony/awt/gl/font/TextRunSegment.java | 165 +++ .../harmony/awt/gl/font/TextRunSegmentImpl.java | 979 +++++++++++++++++ .../awt/gl/image/BufferedImageGraphics2D.java | 79 ++ .../harmony/awt/gl/image/BufferedImageSource.java | 136 +++ .../awt/gl/image/ByteArrayDecodingImageSource.java | 62 ++ .../harmony/awt/gl/image/DataBufferListener.java | 31 + .../harmony/awt/gl/image/DecodingImageSource.java | 261 +++++ .../awt/gl/image/FileDecodingImageSource.java | 68 ++ .../apache/harmony/awt/gl/image/GifDecoder.java | 692 ++++++++++++ .../apache/harmony/awt/gl/image/ImageDecoder.java | 258 +++++ .../apache/harmony/awt/gl/image/ImageLoader.java | 208 ++++ .../apache/harmony/awt/gl/image/JpegDecoder.java | 231 ++++ .../harmony/awt/gl/image/OffscreenImage.java | 532 +++++++++ .../awt/gl/image/OrdinaryWritableRaster.java | 153 +++ .../apache/harmony/awt/gl/image/PngDecoder.java | 270 +++++ .../harmony/awt/gl/image/PngDecoderJava.java | 282 +++++ .../awt/gl/image/URLDecodingImageSource.java | 77 ++ awt/org/apache/harmony/awt/gl/render/Blitter.java | 53 + .../harmony/awt/gl/render/JavaArcRasterizer.java | 502 +++++++++ .../apache/harmony/awt/gl/render/JavaBlitter.java | 611 +++++++++++ .../harmony/awt/gl/render/JavaLineRasterizer.java | 760 +++++++++++++ .../harmony/awt/gl/render/JavaShapeRasterizer.java | 475 ++++++++ .../harmony/awt/gl/render/JavaTextRenderer.java | 263 +++++ .../harmony/awt/gl/render/NativeImageBlitter.java | 218 ++++ .../apache/harmony/awt/gl/render/NullBlitter.java | 56 + .../apache/harmony/awt/im/InputMethodContext.java | 563 ++++++++++ .../apache/harmony/awt/internal/nls/Messages.java | 143 +++ .../apache/harmony/awt/internal/nls/MsgHelp.java | 86 ++ .../apache/harmony/awt/state/MenuItemState.java | 51 + awt/org/apache/harmony/awt/state/MenuState.java | 46 + awt/org/apache/harmony/awt/state/State.java | 55 + awt/org/apache/harmony/awt/wtk/CreationParams.java | 133 +++ awt/org/apache/harmony/awt/wtk/CursorFactory.java | 85 ++ .../apache/harmony/awt/wtk/GraphicsFactory.java | 82 ++ awt/org/apache/harmony/awt/wtk/KeyInfo.java | 53 + awt/org/apache/harmony/awt/wtk/NativeCursor.java | 45 + awt/org/apache/harmony/awt/wtk/NativeEvent.java | 268 +++++ .../apache/harmony/awt/wtk/NativeEventQueue.java | 117 ++ .../apache/harmony/awt/wtk/NativeEventThread.java | 78 ++ awt/org/apache/harmony/awt/wtk/NativeIM.java | 130 +++ .../apache/harmony/awt/wtk/NativeMouseInfo.java | 42 + awt/org/apache/harmony/awt/wtk/NativeRobot.java | 75 ++ awt/org/apache/harmony/awt/wtk/NativeWindow.java | 220 ++++ awt/org/apache/harmony/awt/wtk/ShutdownThread.java | 83 ++ .../apache/harmony/awt/wtk/ShutdownWatchdog.java | 86 ++ awt/org/apache/harmony/awt/wtk/Synchronizer.java | 200 ++++ .../apache/harmony/awt/wtk/SystemProperties.java | 59 + awt/org/apache/harmony/awt/wtk/WTK.java | 61 ++ awt/org/apache/harmony/awt/wtk/WindowFactory.java | 85 ++ .../harmony/beans/internal/nls/Messages.java | 132 +++ .../harmony/beans/internal/nls/messages.properties | 103 ++ .../harmony/x/imageio/internal/nls/Messages.java | 124 +++ .../x/imageio/internal/nls/messages.properties | 18 + .../x/imageio/metadata/IIOMetadataUtils.java | 94 ++ .../plugins/jpeg/IISDecodingImageSource.java | 115 ++ .../harmony/x/imageio/plugins/jpeg/JPEGConsts.java | 44 + .../x/imageio/plugins/jpeg/JPEGImageReader.java | 126 +++ .../x/imageio/plugins/jpeg/JPEGImageReaderSpi.java | 86 ++ .../x/imageio/plugins/jpeg/JPEGImageWriter.java | 402 +++++++ .../x/imageio/plugins/jpeg/JPEGImageWriterSpi.java | 56 + .../x/imageio/plugins/jpeg/JPEGSpiConsts.java | 57 + .../x/imageio/plugins/png/PNGImageReader.java | 106 ++ .../x/imageio/plugins/png/PNGImageReaderSpi.java | 88 ++ .../x/imageio/plugins/png/PNGImageWriter.java | 247 +++++ .../x/imageio/plugins/png/PNGImageWriterParam.java | 41 + .../x/imageio/plugins/png/PNGImageWriterSpi.java | 113 ++ .../apache/harmony/x/imageio/spi/FileIISSpi.java | 53 + .../apache/harmony/x/imageio/spi/FileIOSSpi.java | 52 + .../harmony/x/imageio/spi/InputStreamIISSpi.java | 59 + .../harmony/x/imageio/spi/OutputStreamIOSSpi.java | 60 ++ .../apache/harmony/x/imageio/spi/RAFIISSpi.java | 54 + .../apache/harmony/x/imageio/spi/RAFIOSSpi.java | 53 + .../x/imageio/stream/RandomAccessMemoryCache.java | 226 ++++ 120 files changed, 27214 insertions(+) create mode 100644 awt/org/apache/harmony/awt/ChoiceStyle.java create mode 100644 awt/org/apache/harmony/awt/ClipRegion.java create mode 100644 awt/org/apache/harmony/awt/ComponentInternals.java create mode 100644 awt/org/apache/harmony/awt/ContextStorage.java create mode 100644 awt/org/apache/harmony/awt/ContextThreadGroup.java create mode 100644 awt/org/apache/harmony/awt/ListenerList.java create mode 100644 awt/org/apache/harmony/awt/ReadOnlyIterator.java create mode 100644 awt/org/apache/harmony/awt/gl/AwtImageBackdoorAccessor.java create mode 100644 awt/org/apache/harmony/awt/gl/CommonGraphics2D.java create mode 100644 awt/org/apache/harmony/awt/gl/CommonGraphics2DFactory.java create mode 100644 awt/org/apache/harmony/awt/gl/CommonGraphicsEnvironment.java create mode 100644 awt/org/apache/harmony/awt/gl/Crossing.java create mode 100644 awt/org/apache/harmony/awt/gl/GLVolatileImage.java create mode 100644 awt/org/apache/harmony/awt/gl/ICompositeContext.java create mode 100644 awt/org/apache/harmony/awt/gl/ImageSurface.java create mode 100644 awt/org/apache/harmony/awt/gl/MultiRectArea.java create mode 100644 awt/org/apache/harmony/awt/gl/MultiRectAreaOp.java create mode 100644 awt/org/apache/harmony/awt/gl/Surface.java create mode 100644 awt/org/apache/harmony/awt/gl/TextRenderer.java create mode 100644 awt/org/apache/harmony/awt/gl/XORComposite.java create mode 100644 awt/org/apache/harmony/awt/gl/color/ColorConverter.java create mode 100644 awt/org/apache/harmony/awt/gl/color/ColorScaler.java create mode 100644 awt/org/apache/harmony/awt/gl/color/ICC_ProfileHelper.java create mode 100644 awt/org/apache/harmony/awt/gl/color/ICC_Transform.java create mode 100644 awt/org/apache/harmony/awt/gl/color/LUTColorConverter.java create mode 100644 awt/org/apache/harmony/awt/gl/color/NativeCMM.java create mode 100644 awt/org/apache/harmony/awt/gl/color/NativeImageFormat.java create mode 100644 awt/org/apache/harmony/awt/gl/font/AndroidFont.java create mode 100644 awt/org/apache/harmony/awt/gl/font/AndroidFontManager.java create mode 100644 awt/org/apache/harmony/awt/gl/font/AndroidFontProperty.java create mode 100644 awt/org/apache/harmony/awt/gl/font/AndroidGlyphVector.java create mode 100644 awt/org/apache/harmony/awt/gl/font/AndroidLineMetrics.java create mode 100644 awt/org/apache/harmony/awt/gl/font/BasicMetrics.java create mode 100644 awt/org/apache/harmony/awt/gl/font/CaretManager.java create mode 100644 awt/org/apache/harmony/awt/gl/font/CommonGlyphVector.java create mode 100644 awt/org/apache/harmony/awt/gl/font/CompositeFont.java create mode 100644 awt/org/apache/harmony/awt/gl/font/FontExtraMetrics.java create mode 100644 awt/org/apache/harmony/awt/gl/font/FontFinder.java create mode 100644 awt/org/apache/harmony/awt/gl/font/FontManager.java create mode 100644 awt/org/apache/harmony/awt/gl/font/FontMetricsImpl.java create mode 100644 awt/org/apache/harmony/awt/gl/font/FontPeerImpl.java create mode 100644 awt/org/apache/harmony/awt/gl/font/FontProperty.java create mode 100644 awt/org/apache/harmony/awt/gl/font/Glyph.java create mode 100644 awt/org/apache/harmony/awt/gl/font/LineMetricsImpl.java create mode 100644 awt/org/apache/harmony/awt/gl/font/TextDecorator.java create mode 100644 awt/org/apache/harmony/awt/gl/font/TextMetricsCalculator.java create mode 100644 awt/org/apache/harmony/awt/gl/font/TextRunBreaker.java create mode 100644 awt/org/apache/harmony/awt/gl/font/TextRunSegment.java create mode 100644 awt/org/apache/harmony/awt/gl/font/TextRunSegmentImpl.java create mode 100644 awt/org/apache/harmony/awt/gl/image/BufferedImageGraphics2D.java create mode 100644 awt/org/apache/harmony/awt/gl/image/BufferedImageSource.java create mode 100644 awt/org/apache/harmony/awt/gl/image/ByteArrayDecodingImageSource.java create mode 100644 awt/org/apache/harmony/awt/gl/image/DataBufferListener.java create mode 100644 awt/org/apache/harmony/awt/gl/image/DecodingImageSource.java create mode 100644 awt/org/apache/harmony/awt/gl/image/FileDecodingImageSource.java create mode 100644 awt/org/apache/harmony/awt/gl/image/GifDecoder.java create mode 100644 awt/org/apache/harmony/awt/gl/image/ImageDecoder.java create mode 100644 awt/org/apache/harmony/awt/gl/image/ImageLoader.java create mode 100644 awt/org/apache/harmony/awt/gl/image/JpegDecoder.java create mode 100644 awt/org/apache/harmony/awt/gl/image/OffscreenImage.java create mode 100644 awt/org/apache/harmony/awt/gl/image/OrdinaryWritableRaster.java create mode 100644 awt/org/apache/harmony/awt/gl/image/PngDecoder.java create mode 100644 awt/org/apache/harmony/awt/gl/image/PngDecoderJava.java create mode 100644 awt/org/apache/harmony/awt/gl/image/URLDecodingImageSource.java create mode 100644 awt/org/apache/harmony/awt/gl/render/Blitter.java create mode 100644 awt/org/apache/harmony/awt/gl/render/JavaArcRasterizer.java create mode 100644 awt/org/apache/harmony/awt/gl/render/JavaBlitter.java create mode 100644 awt/org/apache/harmony/awt/gl/render/JavaLineRasterizer.java create mode 100644 awt/org/apache/harmony/awt/gl/render/JavaShapeRasterizer.java create mode 100644 awt/org/apache/harmony/awt/gl/render/JavaTextRenderer.java create mode 100644 awt/org/apache/harmony/awt/gl/render/NativeImageBlitter.java create mode 100644 awt/org/apache/harmony/awt/gl/render/NullBlitter.java create mode 100644 awt/org/apache/harmony/awt/im/InputMethodContext.java create mode 100644 awt/org/apache/harmony/awt/internal/nls/Messages.java create mode 100644 awt/org/apache/harmony/awt/internal/nls/MsgHelp.java create mode 100644 awt/org/apache/harmony/awt/state/MenuItemState.java create mode 100644 awt/org/apache/harmony/awt/state/MenuState.java create mode 100644 awt/org/apache/harmony/awt/state/State.java create mode 100644 awt/org/apache/harmony/awt/wtk/CreationParams.java create mode 100644 awt/org/apache/harmony/awt/wtk/CursorFactory.java create mode 100644 awt/org/apache/harmony/awt/wtk/GraphicsFactory.java create mode 100644 awt/org/apache/harmony/awt/wtk/KeyInfo.java create mode 100644 awt/org/apache/harmony/awt/wtk/NativeCursor.java create mode 100644 awt/org/apache/harmony/awt/wtk/NativeEvent.java create mode 100644 awt/org/apache/harmony/awt/wtk/NativeEventQueue.java create mode 100644 awt/org/apache/harmony/awt/wtk/NativeEventThread.java create mode 100644 awt/org/apache/harmony/awt/wtk/NativeIM.java create mode 100644 awt/org/apache/harmony/awt/wtk/NativeMouseInfo.java create mode 100644 awt/org/apache/harmony/awt/wtk/NativeRobot.java create mode 100644 awt/org/apache/harmony/awt/wtk/NativeWindow.java create mode 100644 awt/org/apache/harmony/awt/wtk/ShutdownThread.java create mode 100644 awt/org/apache/harmony/awt/wtk/ShutdownWatchdog.java create mode 100644 awt/org/apache/harmony/awt/wtk/Synchronizer.java create mode 100644 awt/org/apache/harmony/awt/wtk/SystemProperties.java create mode 100644 awt/org/apache/harmony/awt/wtk/WTK.java create mode 100644 awt/org/apache/harmony/awt/wtk/WindowFactory.java create mode 100644 awt/org/apache/harmony/beans/internal/nls/Messages.java create mode 100644 awt/org/apache/harmony/beans/internal/nls/messages.properties create mode 100644 awt/org/apache/harmony/x/imageio/internal/nls/Messages.java create mode 100644 awt/org/apache/harmony/x/imageio/internal/nls/messages.properties create mode 100644 awt/org/apache/harmony/x/imageio/metadata/IIOMetadataUtils.java create mode 100644 awt/org/apache/harmony/x/imageio/plugins/jpeg/IISDecodingImageSource.java create mode 100644 awt/org/apache/harmony/x/imageio/plugins/jpeg/JPEGConsts.java create mode 100644 awt/org/apache/harmony/x/imageio/plugins/jpeg/JPEGImageReader.java create mode 100644 awt/org/apache/harmony/x/imageio/plugins/jpeg/JPEGImageReaderSpi.java create mode 100644 awt/org/apache/harmony/x/imageio/plugins/jpeg/JPEGImageWriter.java create mode 100644 awt/org/apache/harmony/x/imageio/plugins/jpeg/JPEGImageWriterSpi.java create mode 100644 awt/org/apache/harmony/x/imageio/plugins/jpeg/JPEGSpiConsts.java create mode 100644 awt/org/apache/harmony/x/imageio/plugins/png/PNGImageReader.java create mode 100644 awt/org/apache/harmony/x/imageio/plugins/png/PNGImageReaderSpi.java create mode 100644 awt/org/apache/harmony/x/imageio/plugins/png/PNGImageWriter.java create mode 100644 awt/org/apache/harmony/x/imageio/plugins/png/PNGImageWriterParam.java create mode 100644 awt/org/apache/harmony/x/imageio/plugins/png/PNGImageWriterSpi.java create mode 100644 awt/org/apache/harmony/x/imageio/spi/FileIISSpi.java create mode 100644 awt/org/apache/harmony/x/imageio/spi/FileIOSSpi.java create mode 100644 awt/org/apache/harmony/x/imageio/spi/InputStreamIISSpi.java create mode 100644 awt/org/apache/harmony/x/imageio/spi/OutputStreamIOSSpi.java create mode 100644 awt/org/apache/harmony/x/imageio/spi/RAFIISSpi.java create mode 100644 awt/org/apache/harmony/x/imageio/spi/RAFIOSSpi.java create mode 100644 awt/org/apache/harmony/x/imageio/stream/RandomAccessMemoryCache.java (limited to 'awt/org/apache') diff --git a/awt/org/apache/harmony/awt/ChoiceStyle.java b/awt/org/apache/harmony/awt/ChoiceStyle.java new file mode 100644 index 0000000..93b7aad --- /dev/null +++ b/awt/org/apache/harmony/awt/ChoiceStyle.java @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Dmitry A. Durnev + * @version $Revision$ + */ +package org.apache.harmony.awt; + +/** + * ChoiceStyle. + * Is used to define custom choice properties: + * width and x screen coordinate of the list popup window. + */ +public interface ChoiceStyle { + + int getPopupX(int x, int width, int choiceWidth, int screenWidth); + int getPopupWidth(int choiceWidth); + +} diff --git a/awt/org/apache/harmony/awt/ClipRegion.java b/awt/org/apache/harmony/awt/ClipRegion.java new file mode 100644 index 0000000..c89a81d --- /dev/null +++ b/awt/org/apache/harmony/awt/ClipRegion.java @@ -0,0 +1,84 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Pavel Dolgov, Anton Avtamonov + * @version $Revision$ + */ +package org.apache.harmony.awt; + +import java.awt.Component; +import java.awt.Rectangle; + +import org.apache.harmony.awt.gl.MultiRectArea; +import org.apache.harmony.awt.internal.nls.Messages; + +public class ClipRegion extends Rectangle { + private final MultiRectArea clip; + + public ClipRegion(final MultiRectArea clip) { + this.clip = new MultiRectArea(clip); + setBounds(clip.getBounds()); + } + + public MultiRectArea getClip() { + return clip; + } + + @Override + public String toString() { + String str = clip.toString(); + int i = str.indexOf('['); + str = str.substring(i); + if (clip.getRectCount() == 1) { + str = str.substring(1, str.length() - 1); + } + return getClass().getName() + str; + } + + + public void convertRegion(final Component child, final Component parent) { + convertRegion(child, clip, parent); + } + + public void intersect(final Rectangle rect) { + clip.intersect(rect); + } + + @Override + public boolean isEmpty() { + return clip.isEmpty(); + } + + public static void convertRegion(final Component child, + final MultiRectArea region, + final Component parent) { + int x = 0, y = 0; + Component c = child; + //???AWT + /* + for (; c != null && c != parent; c = c.getParent()) { + x += c.getX(); + y += c.getY(); + } + */ + if (c == null) { + // awt.51=Component expected to be a parent + throw new IllegalArgumentException(Messages.getString("awt.51")); //$NON-NLS-1$ + } + region.translate(x, y); + } +} diff --git a/awt/org/apache/harmony/awt/ComponentInternals.java b/awt/org/apache/harmony/awt/ComponentInternals.java new file mode 100644 index 0000000..c359784 --- /dev/null +++ b/awt/org/apache/harmony/awt/ComponentInternals.java @@ -0,0 +1,212 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Pavel Dolgov + * @version $Revision$ + */ +package org.apache.harmony.awt; + +//???AWT +//import java.awt.Component; +//import java.awt.Container; +//import java.awt.Dialog; +import java.awt.Dimension; +//import java.awt.Image; +import java.awt.Insets; +import java.awt.Point; +import java.awt.Rectangle; +//import java.awt.Window; +//import java.awt.Choice; +import java.lang.reflect.InvocationTargetException; + +import org.apache.harmony.awt.gl.MultiRectArea; +//import org.apache.harmony.awt.text.TextFieldKit; +//import org.apache.harmony.awt.text.TextKit; +//import org.apache.harmony.awt.wtk.NativeWindow; + +import org.apache.harmony.luni.util.NotImplementedException; + +/** + * The accessor to AWT private API + */ +public abstract class ComponentInternals { + + /** + * @return the ComponentInternals instance to serve the requests + */ + public static ComponentInternals getComponentInternals() { + return ContextStorage.getComponentInternals(); + } + + /** + * This method must be called by AWT to establish the connection + * @param internals - implementation of ComponentInternals created by AWT + */ + public static void setComponentInternals(ComponentInternals internals) { + ContextStorage.setComponentInternals(internals); + } + + /** + * The accessor to native resource connected to a component. + * It returns non-null value only if component + * already has the native resource + */ + //public abstract NativeWindow getNativeWindow(Component component); + + /** + * Connect Window object to existing native resource + * @param nativeWindowId - id of native window to attach + * @return Window object with special behaviour that + * restricts manupulation with that window + */ + //public abstract Window attachNativeWindow(long nativeWindowId); + + /** + * Start mouse grab in "client" mode. + * All mouse events in AWT components will be reported as usual, + * mouse events that occured outside of AWT components will be sent to + * the window passed as grabWindow parameter. When mouse grab is canceled + * (because of click in non-AWT window or by task switching) + * the whenCanceled callback is called + * + * @param grabWindow - window that will own the grab + * @param whenCanceled - callback called when grab is canceled by user's action + */ + //public abstract void startMouseGrab(Window grabWindow, Runnable whenCanceled); + + /** + * End mouse grab and resume normal processing of mouse events + */ + //public abstract void endMouseGrab(); + + /** + * Set the popup flag of the window to true. + * This window won't be controlled by window manager on Linux. + * Call this method before the window is shown first time + * @param window - the window that should become popup one + */ + //public abstract void makePopup(Window window); + + /** + * This method must be called by Graphics at the beginning of drawImage() + * to store image drawing parameters (defined by application developer) in component + * + * @param comp - component that draws the image + * @param image - image to be drawn + * @param destLocation - location of the image upon the component's surface. Never null. + * @param destSize - size of the component's area to be filled with the image. + * Equals to null if size parameters omitted in drawImage. + * @param source - area of the image to be drawn on the component. + * Equals to null if src parameters omitted in drawImage. + */ + /* + public abstract void onDrawImage(Component comp, Image image, Point destLocation, + Dimension destSize, Rectangle source); +*/ + /** + * Sets system's caret position. + * This method should be called by text component to synchronize our caret position + * with system's caret position. + * @param x + * @param y + */ + //public abstract void setCaretPos(Component c, int x, int y); + + /** + * NEVER USE IT. FORGET IT. IT DOES NOT EXIST. + * See Toolkit.unsafeInvokeAndWait(Runnable). + * + * Accessor for Toolkit.unsafeInvokeAndWait(Runnable) method. + * For use in exceptional cases only. + * Read comments for Toolkit.unsafeInvokeAndWait(Runnable) before use. + */ + /* + public abstract void unsafeInvokeAndWait(Runnable runnable) + throws InterruptedException, InvocationTargetException; + + public abstract TextKit getTextKit(Component comp); + + public abstract void setTextKit(Component comp, TextKit kit); + + public abstract TextFieldKit getTextFieldKit(Component comp); + + public abstract void setTextFieldKit(Component comp, TextFieldKit kit); +*/ + /** + * Terminate event dispatch thread, completely destroy AWT context.
+ * Intended for multi-context mode, in single-context mode does nothing. + * + */ + public abstract void shutdown(); + + /** + * Sets mouse events preprocessor for event queue + */ + //public abstract void setMouseEventPreprocessor(MouseEventPreprocessor preprocessor); + + /** + * Create customized Choice using style + */ + //public abstract Choice createCustomChoice(ChoiceStyle style); + + //public abstract Insets getNativeInsets(Window w); + + /** + * Region to be repainted (could be null). Use this in overridden repaint() + */ + //public abstract MultiRectArea getRepaintRegion(Component c); + + //public abstract MultiRectArea subtractPendingRepaintRegion(Component c, MultiRectArea mra); + + /** + * Returns true if the window was at least once painted due to native paint events + */ + //public abstract boolean wasPainted(Window w); + + /** + * The component's region hidden behind top-level windows + * (belonging to both this Java app and all other apps), and behind + * heavyweight components overlapping with passed component + */ + //public abstract MultiRectArea getObscuredRegion(Component c); + + /** + * An accessor to Container.addObscuredRegions() method + * @see java.awt.Container#addObscuredRegions(MultiRectArea, Component) + */ + //public abstract void addObscuredRegions(MultiRectArea mra, Component c, Container container); + + /** + * Makes it possible to call protected Toolkit.setDesktopProperty() + * method from any class outside of java.awt package + */ + public abstract void setDesktopProperty(String name, Object value); + + /** + * Makes it possible to start/stop dialog modal loop + * from anywhere outside of java.awt package + */ + //public abstract void runModalLoop(Dialog dlg); + //public abstract void endModalLoop(Dialog dlg); + + /** + * Sets component's visible flag only + * (the component is not actually shown/hidden) + */ + //public abstract void setVisibleFlag(Component comp, boolean visible); + +} diff --git a/awt/org/apache/harmony/awt/ContextStorage.java b/awt/org/apache/harmony/awt/ContextStorage.java new file mode 100644 index 0000000..d44648a --- /dev/null +++ b/awt/org/apache/harmony/awt/ContextStorage.java @@ -0,0 +1,154 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Pavel Dolgov + * @version $Revision$ + */ +package org.apache.harmony.awt; + +import java.awt.*; + +//???AWT +//import org.apache.harmony.awt.datatransfer.*; +import org.apache.harmony.awt.internal.nls.Messages; +import org.apache.harmony.awt.wtk.*; + + +public final class ContextStorage { + + private static volatile boolean multiContextMode = false; + private volatile boolean shutdownPending = false; + + private static final ContextStorage globalContext = new ContextStorage(); + + private Toolkit toolkit; + private ComponentInternals componentInternals; + //???AWT: private DTK dtk; + private WTK wtk; + private GraphicsEnvironment graphicsEnvironment; + + private class ContextLock {} + private final Object contextLock = new ContextLock(); + private final Synchronizer synchronizer = new Synchronizer(); + + public static void activateMultiContextMode() { + // TODO: checkPermission + multiContextMode = true; + } + + public static void setDefaultToolkit(Toolkit newToolkit) { + // TODO: checkPermission + getCurrentContext().toolkit = newToolkit; + } + + public static Toolkit getDefaultToolkit() { + return getCurrentContext().toolkit; + } + + //???AWT + /* + public static void setDTK(DTK dtk) { + // TODO: checkPermission + getCurrentContext().dtk = dtk; + } + + public static DTK getDTK() { + return getCurrentContext().dtk; + } + */ + + public static Synchronizer getSynchronizer() { + return getCurrentContext().synchronizer; + } + + public static ComponentInternals getComponentInternals() { + return getCurrentContext().componentInternals; + } + + static void setComponentInternals(ComponentInternals internals) { + // TODO: checkPermission + getCurrentContext().componentInternals = internals; + } + + public static Object getContextLock() { + return getCurrentContext().contextLock; + } + + public static WindowFactory getWindowFactory() { + return getCurrentContext().wtk.getWindowFactory(); + } + + public static void setWTK(WTK wtk) { + getCurrentContext().wtk = wtk; + } + + public static NativeIM getNativeIM() { + return getCurrentContext().wtk.getNativeIM(); + } + + public static NativeEventQueue getNativeEventQueue() { + return getCurrentContext().wtk.getNativeEventQueue(); + } + + public static GraphicsEnvironment getGraphicsEnvironment() { + return getCurrentContext().graphicsEnvironment; + } + + public static void setGraphicsEnvironment(GraphicsEnvironment environment) { + getCurrentContext().graphicsEnvironment = environment; + } + + private static ContextStorage getCurrentContext() { + return multiContextMode ? getContextThreadGroup().context : globalContext; + } + + private static ContextThreadGroup getContextThreadGroup() { + + Thread thread = Thread.currentThread(); + ThreadGroup group = thread.getThreadGroup(); + while (group != null) { + if (group instanceof ContextThreadGroup) { + return (ContextThreadGroup)group; + } + group = group.getParent(); + } + // awt.59=Application has run out of context thread group + throw new RuntimeException(Messages.getString("awt.59")); //$NON-NLS-1$ + } + + public static boolean shutdownPending() { + return getCurrentContext().shutdownPending; + } + + void shutdown() { + if (!multiContextMode) { + return; + } + shutdownPending = true; + + //???AWT: componentInternals.shutdown(); + + synchronized(contextLock) { + toolkit = null; + componentInternals = null; + //???AWT: dtk = null; + wtk = null; + graphicsEnvironment = null; + } + } + +} diff --git a/awt/org/apache/harmony/awt/ContextThreadGroup.java b/awt/org/apache/harmony/awt/ContextThreadGroup.java new file mode 100644 index 0000000..4f0af52 --- /dev/null +++ b/awt/org/apache/harmony/awt/ContextThreadGroup.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Pavel Dolgov + * @version $Revision$ + */ +package org.apache.harmony.awt; + +public class ContextThreadGroup extends ThreadGroup { + + final ContextStorage context = new ContextStorage(); + + public ContextThreadGroup(String name) { + super(name); + } + + public void dispose() { + context.shutdown(); + } +} diff --git a/awt/org/apache/harmony/awt/ListenerList.java b/awt/org/apache/harmony/awt/ListenerList.java new file mode 100644 index 0000000..f5c55f1 --- /dev/null +++ b/awt/org/apache/harmony/awt/ListenerList.java @@ -0,0 +1,194 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.harmony.awt; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.EventListener; +import java.util.Iterator; +import java.util.List; + +/** + * List of AWT listeners. It is for 3 purposes. + * 1. To support list modification from listeners + * 2. To ensure call for all listeners as atomic operation + * 3. To support system listeners that are needed for built-in AWT components + */ +public class ListenerList implements Serializable { + private static final long serialVersionUID = 9180703263299648154L; + + private transient ArrayList systemList; + private transient ArrayList userList; + + public ListenerList() { + super(); + } + + /** + * Adds system listener to this list. + * + * @param listener - listener to be added. + */ + public void addSystemListener(T listener) { + if (systemList == null) { + systemList = new ArrayList(); + } + systemList.add(listener); + } + + /** + * Adds user (public) listener to this list. + * + * @param listener - listener to be added. + */ + public void addUserListener(T listener) { + if (listener == null) { + return; + } + // transactionally replace old list + synchronized (this) { + if (userList == null) { + userList = new ArrayList(); + userList.add(listener); + return; + } + ArrayList newList = new ArrayList(userList); + newList.add(listener); + userList = newList; + } + } + + /** + * Removes user (public) listener to this list. + * + * @param listener - listener to be removed. + */ + public void removeUserListener(Object listener) { + if (listener == null) { + return; + } + // transactionally replace old list + synchronized (this) { + if (userList == null || !userList.contains(listener)) { + return; + } + ArrayList newList = new ArrayList(userList); + newList.remove(listener); + userList = (newList.size() > 0 ? newList : null); + } + } + + /** + * Gets all user (public) listeners in one array. + * + * @param emptyArray - empty array, it's for deriving particular listeners class. + * @return array of all user listeners. + */ + public AT[] getUserListeners(AT[] emptyArray){ + synchronized (this) { + return (userList != null ? userList.toArray(emptyArray) : emptyArray); + + } + } + + /** + * Gets all user (public) listeners in one list. + * + * @return list of all user listeners. + */ + public List getUserListeners() { + synchronized (this) { + if (userList == null || userList.isEmpty()) { + return Collections.emptyList(); + } + return new ArrayList(userList); + } + } + + public List getSystemListeners() { + synchronized (this) { + if (systemList == null || systemList.isEmpty()) { + return Collections.emptyList(); + } + return new ArrayList(systemList); + } + } + + /** + * Gets iterator for user listeners. + * + * @return iterator for user listeners. + */ + public Iterator getUserIterator() { + synchronized (this) { + if (userList == null) { + List emptyList = Collections.emptyList(); + return emptyList.iterator(); + } + return new ReadOnlyIterator(userList.iterator()); + } + } + + /** + * Gets iterator for system listeners. + * + * @return iterator for system listeners. + */ + public Iterator getSystemIterator() { + return systemList.iterator(); + } + + private static ArrayList getOnlySerializable(ArrayList list) { + if (list == null) { + return null; + } + + ArrayList result = new ArrayList(); + for (Iterator it = list.iterator(); it.hasNext();) { + Object obj = it.next(); + if (obj instanceof Serializable) { + result.add(obj); + } + } + + return (result.size() != 0) ? result : null; + } + + private void writeObject(ObjectOutputStream stream) throws IOException { + + stream.defaultWriteObject(); + + stream.writeObject(getOnlySerializable(systemList)); + stream.writeObject(getOnlySerializable(userList)); + } + + @SuppressWarnings("unchecked") + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException { + + stream.defaultReadObject(); + + systemList = (ArrayList)stream.readObject(); + userList = (ArrayList)stream.readObject(); + } + +} diff --git a/awt/org/apache/harmony/awt/ReadOnlyIterator.java b/awt/org/apache/harmony/awt/ReadOnlyIterator.java new file mode 100644 index 0000000..671653f --- /dev/null +++ b/awt/org/apache/harmony/awt/ReadOnlyIterator.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Pavel Dolgov + * @version $Revision$ + */ +package org.apache.harmony.awt; + +import java.util.Iterator; + +import org.apache.harmony.awt.internal.nls.Messages; + +/** + * ReadOnlyIterator + */ +public final class ReadOnlyIterator implements Iterator { + + private final Iterator it; + + public ReadOnlyIterator(Iterator it) { + if (it == null) { + throw new NullPointerException(); + } + this.it = it; + } + + public void remove() { + // awt.50=Iterator is read-only + throw new UnsupportedOperationException(Messages.getString("awt.50")); //$NON-NLS-1$ + } + + public boolean hasNext() { + return it.hasNext(); + } + + public E next() { + return it.next(); + } +} diff --git a/awt/org/apache/harmony/awt/gl/AwtImageBackdoorAccessor.java b/awt/org/apache/harmony/awt/gl/AwtImageBackdoorAccessor.java new file mode 100644 index 0000000..bd5f6c6 --- /dev/null +++ b/awt/org/apache/harmony/awt/gl/AwtImageBackdoorAccessor.java @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Igor V. Stolyarov + * @version $Revision$ + * Created on 23.11.2005 + * + */ + + +package org.apache.harmony.awt.gl; + +import java.awt.Image; +import java.awt.image.DataBuffer; +import java.awt.image.IndexColorModel; +import java.awt.image.DataBufferInt; + +import org.apache.harmony.awt.gl.image.DataBufferListener; + +/** + * This class give an opportunity to get access to private data of + * some java.awt.image classes + * Implementation of this class placed in java.awt.image package + */ + +public abstract class AwtImageBackdoorAccessor { + + static protected AwtImageBackdoorAccessor inst; + + public static AwtImageBackdoorAccessor getInstance(){ + // First we need to run the static initializer in the DataBuffer class to resolve inst. + new DataBufferInt(0); + return inst; + } + + public abstract Surface getImageSurface(Image image); + public abstract boolean isGrayPallete(IndexColorModel icm); + + public abstract Object getData(DataBuffer db); + public abstract int[] getDataInt(DataBuffer db); + public abstract byte[] getDataByte(DataBuffer db); + public abstract short[] getDataShort(DataBuffer db); + public abstract short[] getDataUShort(DataBuffer db); + public abstract double[] getDataDouble(DataBuffer db); + public abstract float[] getDataFloat(DataBuffer db); + public abstract void releaseData(DataBuffer db); + + public abstract void addDataBufferListener(DataBuffer db, DataBufferListener listener); + public abstract void removeDataBufferListener(DataBuffer db); + public abstract void validate(DataBuffer db); +} diff --git a/awt/org/apache/harmony/awt/gl/CommonGraphics2D.java b/awt/org/apache/harmony/awt/gl/CommonGraphics2D.java new file mode 100644 index 0000000..a33c38b --- /dev/null +++ b/awt/org/apache/harmony/awt/gl/CommonGraphics2D.java @@ -0,0 +1,1132 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Alexey A. Petrenko + * @version $Revision$ + */ +package org.apache.harmony.awt.gl; + + +import java.awt.AlphaComposite; +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Composite; +import java.awt.Font; +import java.awt.FontMetrics; +import java.awt.Graphics2D; +import java.awt.Image; +import java.awt.Paint; +import java.awt.PaintContext; +import java.awt.Point; +import java.awt.Polygon; +import java.awt.Rectangle; +import java.awt.RenderingHints; +import java.awt.Shape; +import java.awt.Stroke; +import java.awt.Toolkit; +import java.awt.font.FontRenderContext; +import java.awt.font.GlyphVector; +import java.awt.image.AffineTransformOp; +import java.awt.image.ImageObserver; +import java.awt.image.BufferedImage; +import java.awt.image.BufferedImageOp; +import java.awt.image.Raster; +import java.awt.image.RenderedImage; +import java.awt.image.WritableRaster; +import java.awt.image.renderable.RenderableImage; +import java.awt.geom.AffineTransform; +import java.awt.geom.Arc2D; +import java.awt.geom.Ellipse2D; +import java.awt.geom.Line2D; +import java.awt.geom.PathIterator; +import java.awt.geom.RoundRectangle2D; +import java.text.AttributedCharacterIterator; +import java.util.Map; + +import org.apache.harmony.awt.gl.Surface; +import org.apache.harmony.awt.gl.image.OffscreenImage; +import org.apache.harmony.awt.gl.render.Blitter; +import org.apache.harmony.awt.gl.render.JavaArcRasterizer; +import org.apache.harmony.awt.gl.render.JavaLineRasterizer; +import org.apache.harmony.awt.gl.render.JavaShapeRasterizer; +import org.apache.harmony.awt.gl.render.JavaTextRenderer; +import org.apache.harmony.awt.gl.render.NullBlitter; + +/* + * List of abstract methods to implement in subclusses + * Graphics.copyArea(int x, int y, int width, int height, int dx, int dy) + * Graphics.create() + * Graphics2D.getDeviceConfiguration() + * CommonGraphics2D.fillMultiRectAreaColor(MultiRectArea mra); + * CommonGraphics2D.fillMultiRectAreaPaint(MultiRectArea mra); + */ + +/** + * CommonGraphics2D class is a super class for all system-dependent + * implementations. It implements major part of Graphics and Graphics2D + * abstract methods. + *

CommonGraphics2D Class Internals

+ *

Line and Shape Rasterizers

+ *

+ * The CommonGraphics2D class splits all shapes into a set of rectangles + * to unify the drawing process for different operating systems and architectures. + * For this purpose Java 2D* uses the JavaShapeRasterizer and the JavaLineRasterizer + * classes from the org.apache.harmony.awt.gl.render package. The JavaShapeRasterizer + * class splits an object implementing a Shape interface into a set of rectangles and + * produces a MultiRectArea object. The JavaLineRasterizer class makes line drawing + * more accurate and processes lines with strokes, which are instances of the BasicStroke + * class. + *

+ *

+ * To port the shape drawing to another platform you just need to override + * rectangle-drawing methods. However, if your operating system has functions to draw + * particular shapes, you can optimize your subclass of the CommonGraphics2D class by + * using this functionality in overridden methods. + *

+ + *

Blitters

+ *

+ * Blitter classes draw images on the display or buffered images. All blitters inherit + * the org.apache.harmony.awt.gl.render.Blitter interface. + *

+ *

Blitters are divided into: + *

    + *
  • Native blitters for simple types of images, which the underlying native library + * can draw.
  • + *
  • Java* blitters for those types of images, which the underlying native library + * cannot handle.
  • + *

+ *

+ * DRL Java 2D* also uses blitters to fill the shapes and the user-defined subclasses + * of the java.awt.Paint class with paints, which the system does not support. + *

+ * + *

Text Renderers

+ *

+ *Text renderers draw strings and glyph vectors. All text renderers are subclasses + *of the org.apache.harmony.awt.gl.TextRenderer class. + *

+ * + */ +public abstract class CommonGraphics2D extends Graphics2D { + protected Surface dstSurf = null; + protected Blitter blitter = NullBlitter.getInstance(); + protected RenderingHints hints = new RenderingHints(null); + + // Clipping things + protected MultiRectArea clip = null; + + protected Paint paint = Color.WHITE; + protected Color fgColor = Color.WHITE; + protected Color bgColor = Color.BLACK; + + protected Composite composite = AlphaComposite.SrcOver; + + protected Stroke stroke = new BasicStroke(); + + //TODO: Think more about FontRenderContext + protected FontRenderContext frc = new FontRenderContext(null, false, false); + + protected JavaShapeRasterizer jsr = new JavaShapeRasterizer(); + + protected Font font = new Font("Dialog", Font.PLAIN, 12);; //$NON-NLS-1$ + + protected TextRenderer jtr = JavaTextRenderer.inst; + + // Current graphics transform + protected AffineTransform transform = new AffineTransform(); + protected double[] matrix = new double[6]; + + // Original user->device translation as transform and point + //public AffineTransform origTransform = new AffineTransform(); + public Point origPoint = new Point(0, 0); + + + // Print debug output or not + protected static final boolean debugOutput = "1".equals(System.getProperty("g2d.debug")); //$NON-NLS-1$ //$NON-NLS-2$ + + // Constructors + protected CommonGraphics2D() { + } + + protected CommonGraphics2D(int tx, int ty) { + this(tx, ty, null); + } + + protected CommonGraphics2D(int tx, int ty, MultiRectArea clip) { + setTransform(AffineTransform.getTranslateInstance(tx, ty)); + //origTransform = AffineTransform.getTranslateInstance(tx, ty); + origPoint = new Point(tx, ty); + setClip(clip); + } + + // Public methods + @Override + public void addRenderingHints(Map hints) { + this.hints.putAll(hints); + } + + @Override + public void clearRect(int x, int y, int width, int height) { + Color c = getColor(); + Paint p = getPaint(); + setColor(getBackground()); + fillRect(x, y, width, height); + setColor(c); + setPaint(p); + if (debugOutput) { + System.err.println("CommonGraphics2D.clearRect("+x+", "+y+", "+width+", "+height+")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ + } + } + + @Override + public void clipRect(int x, int y, int width, int height) { + clip(new Rectangle(x, y, width, height)); + } + + + @Override + public void clip(Shape s) { + if (s == null) { + clip = null; + return; + } + + MultiRectArea mra = null; + if (s instanceof MultiRectArea) { + mra = new MultiRectArea((MultiRectArea)s); + mra.translate((int)transform.getTranslateX(), (int)transform.getTranslateY()); + } else { + int type = transform.getType(); + if(s instanceof Rectangle && (type & (AffineTransform.TYPE_IDENTITY | + AffineTransform.TYPE_TRANSLATION)) != 0){ + mra = new MultiRectArea((Rectangle)s); + if(type == AffineTransform.TYPE_TRANSLATION){ + mra.translate((int)transform.getTranslateX(), (int)transform.getTranslateY()); + } + } else { + s = transform.createTransformedShape(s); + mra = jsr.rasterize(s, 0.5); + } + } + + if (clip == null) { + setTransformedClip(mra); + } else { + clip.intersect(mra); + setTransformedClip(clip); + } + } + + @Override + public void dispose() { + // Do nothing for Java only classes + } + + + + + /*************************************************************************** + * + * Draw methods + * + ***************************************************************************/ + + @Override + public void draw(Shape s) { + if (stroke instanceof BasicStroke && ((BasicStroke)stroke).getLineWidth() <= 1) { + //TODO: Think about drawing the shape in one fillMultiRectArea call + BasicStroke bstroke = (BasicStroke)stroke; + JavaLineRasterizer.LineDasher ld = (bstroke.getDashArray() == null)?null:new JavaLineRasterizer.LineDasher(bstroke.getDashArray(), bstroke.getDashPhase()); + PathIterator pi = s.getPathIterator(transform, 0.5); + float []points = new float[6]; + int x1 = Integer.MIN_VALUE; + int y1 = Integer.MIN_VALUE; + int cx1 = Integer.MIN_VALUE; + int cy1 = Integer.MIN_VALUE; + while (!pi.isDone()) { + switch (pi.currentSegment(points)) { + case PathIterator.SEG_MOVETO: + x1 = (int)Math.floor(points[0]); + y1 = (int)Math.floor(points[1]); + cx1 = x1; + cy1 = y1; + break; + case PathIterator.SEG_LINETO: + int x2 = (int)Math.floor(points[0]); + int y2 = (int)Math.floor(points[1]); + fillMultiRectArea(JavaLineRasterizer.rasterize(x1, y1, x2, y2, null, ld, false)); + x1 = x2; + y1 = y2; + break; + case PathIterator.SEG_CLOSE: + x2 = cx1; + y2 = cy1; + fillMultiRectArea(JavaLineRasterizer.rasterize(x1, y1, x2, y2, null, ld, false)); + x1 = x2; + y1 = y2; + break; + } + pi.next(); + } + } else { + s = stroke.createStrokedShape(s); + s = transform.createTransformedShape(s); + fillMultiRectArea(jsr.rasterize(s, 0.5)); + } + } + + @Override + public void drawArc(int x, int y, int width, int height, int sa, int ea) { + if (stroke instanceof BasicStroke && ((BasicStroke)stroke).getLineWidth() <= 1 && + ((BasicStroke)stroke).getDashArray() == null && + (transform.isIdentity() || transform.getType() == AffineTransform.TYPE_TRANSLATION)) { + Point p = new Point(x, y); + transform.transform(p, p); + MultiRectArea mra = JavaArcRasterizer.rasterize(x, y, width, height, sa, ea, clip); + fillMultiRectArea(mra); + return; + } + draw(new Arc2D.Float(x, y, width, height, sa, ea, Arc2D.OPEN)); + } + + + @Override + public boolean drawImage(Image image, int x, int y, Color bgcolor, + ImageObserver imageObserver) { + + if(image == null) { + return true; + } + + boolean done = false; + boolean somebits = false; + Surface srcSurf = null; + if(image instanceof OffscreenImage){ + OffscreenImage oi = (OffscreenImage) image; + if((oi.getState() & ImageObserver.ERROR) != 0) { + return false; + } + done = oi.prepareImage(imageObserver); + somebits = (oi.getState() & ImageObserver.SOMEBITS) != 0; + srcSurf = oi.getImageSurface(); + }else{ + done = true; + srcSurf = Surface.getImageSurface(image); + } + + if(done || somebits) { + int w = srcSurf.getWidth(); + int h = srcSurf.getHeight(); + blitter.blit(0, 0, srcSurf, x, y, dstSurf, w, h, (AffineTransform) transform.clone(), + composite, bgcolor, clip); + } + return done; + } + + @Override + public boolean drawImage(Image image, int x, int y, ImageObserver imageObserver) { + return drawImage(image, x, y, null, imageObserver); + } + + @Override + public boolean drawImage(Image image, int x, int y, int width, int height, + Color bgcolor, ImageObserver imageObserver) { + + if(image == null) { + return true; + } + if(width == 0 || height == 0) { + return true; + } + + boolean done = false; + boolean somebits = false; + Surface srcSurf = null; + + if(image instanceof OffscreenImage){ + OffscreenImage oi = (OffscreenImage) image; + if((oi.getState() & ImageObserver.ERROR) != 0) { + return false; + } + done = oi.prepareImage(imageObserver); + somebits = (oi.getState() & ImageObserver.SOMEBITS) != 0; + srcSurf = oi.getImageSurface(); + }else{ + done = true; + srcSurf = Surface.getImageSurface(image); + } + + if(done || somebits) { + int w = srcSurf.getWidth(); + int h = srcSurf.getHeight(); + if(w == width && h == height){ + blitter.blit(0, 0, srcSurf, x, y, dstSurf, w, h, + (AffineTransform) transform.clone(), + composite, bgcolor, clip); + }else{ + AffineTransform xform = new AffineTransform(); + xform.setToScale((float)width / w, (float)height / h); + blitter.blit(0, 0, srcSurf, x, y, dstSurf, w, h, + (AffineTransform) transform.clone(), + xform, composite, bgcolor, clip); + } + } + return done; + } + + @Override + public boolean drawImage(Image image, int x, int y, int width, int height, + ImageObserver imageObserver) { + return drawImage(image, x, y, width, height, null, imageObserver); + } + + @Override + public boolean drawImage(Image image, int dx1, int dy1, int dx2, int dy2, + int sx1, int sy1, int sx2, int sy2, Color bgcolor, + ImageObserver imageObserver) { + + if(image == null) { + return true; + } + if(dx1 == dx2 || dy1 == dy2 || sx1 == sx2 || sy1 == sy2) { + return true; + } + + boolean done = false; + boolean somebits = false; + Surface srcSurf = null; + if(image instanceof OffscreenImage){ + OffscreenImage oi = (OffscreenImage) image; + if((oi.getState() & ImageObserver.ERROR) != 0) { + return false; + } + done = oi.prepareImage(imageObserver); + somebits = (oi.getState() & ImageObserver.SOMEBITS) != 0; + srcSurf = oi.getImageSurface(); + }else{ + done = true; + srcSurf = Surface.getImageSurface(image); + } + + if(done || somebits) { + + int dstX = dx1; + int dstY = dy1; + int srcX = sx1; + int srcY = sy1; + + int dstW = dx2 - dx1; + int dstH = dy2 - dy1; + int srcW = sx2 - sx1; + int srcH = sy2 - sy1; + + if(srcW == dstW && srcH == dstH){ + blitter.blit(srcX, srcY, srcSurf, dstX, dstY, dstSurf, srcW, srcH, + (AffineTransform) transform.clone(), + composite, bgcolor, clip); + }else{ + AffineTransform xform = new AffineTransform(); + xform.setToScale((float)dstW / srcW, (float)dstH / srcH); + blitter.blit(srcX, srcY, srcSurf, dstX, dstY, dstSurf, srcW, srcH, + (AffineTransform) transform.clone(), + xform, composite, bgcolor, clip); + } + } + return done; + } + + @Override + public boolean drawImage(Image image, int dx1, int dy1, int dx2, int dy2, + int sx1, int sy1, int sx2, int sy2, ImageObserver imageObserver) { + + return drawImage(image, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, null, + imageObserver); + } + + @Override + public void drawImage(BufferedImage bufImage, BufferedImageOp op, + int x, int y) { + + if(bufImage == null) { + return; + } + + if(op == null) { + drawImage(bufImage, x, y, null); + } else if(op instanceof AffineTransformOp){ + AffineTransformOp atop = (AffineTransformOp) op; + AffineTransform xform = atop.getTransform(); + Surface srcSurf = Surface.getImageSurface(bufImage); + int w = srcSurf.getWidth(); + int h = srcSurf.getHeight(); + blitter.blit(0, 0, srcSurf, x, y, dstSurf, w, h, + (AffineTransform) transform.clone(), xform, + composite, null, clip); + } else { + bufImage = op.filter(bufImage, null); + Surface srcSurf = Surface.getImageSurface(bufImage); + int w = srcSurf.getWidth(); + int h = srcSurf.getHeight(); + blitter.blit(0, 0, srcSurf, x, y, dstSurf, w, h, + (AffineTransform) transform.clone(), + composite, null, clip); + } + } + + @Override + public boolean drawImage(Image image, AffineTransform trans, + ImageObserver imageObserver) { + + if(image == null) { + return true; + } + if(trans == null || trans.isIdentity()) { + return drawImage(image, 0, 0, imageObserver); + } + + boolean done = false; + boolean somebits = false; + Surface srcSurf = null; + if(image instanceof OffscreenImage){ + OffscreenImage oi = (OffscreenImage) image; + if((oi.getState() & ImageObserver.ERROR) != 0) { + return false; + } + done = oi.prepareImage(imageObserver); + somebits = (oi.getState() & ImageObserver.SOMEBITS) != 0; + srcSurf = oi.getImageSurface(); + }else{ + done = true; + srcSurf = Surface.getImageSurface(image); + } + + if(done || somebits) { + int w = srcSurf.getWidth(); + int h = srcSurf.getHeight(); + AffineTransform xform = (AffineTransform) transform.clone(); + xform.concatenate(trans); + blitter.blit(0, 0, srcSurf, 0, 0, dstSurf, w, h, xform, composite, + null, clip); + } + return done; + } + + @Override + public void drawLine(int x1, int y1, int x2, int y2) { + if (debugOutput) { + System.err.println("CommonGraphics2D.drawLine("+x1+", "+y1+", "+x2+", "+y2+")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ + } + + if (stroke instanceof BasicStroke && ((BasicStroke)stroke).getLineWidth() <= 1) { + BasicStroke bstroke = (BasicStroke)stroke; + Point p1 = new Point(x1, y1); + Point p2 = new Point(x2, y2); + transform.transform(p1, p1); + transform.transform(p2, p2); + JavaLineRasterizer.LineDasher ld = (bstroke.getDashArray() == null)?null:new JavaLineRasterizer.LineDasher(bstroke.getDashArray(), bstroke.getDashPhase()); + MultiRectArea mra = JavaLineRasterizer.rasterize(p1.x, p1.y, p2.x, p2.y, null, ld, false); + fillMultiRectArea(mra); + return; + } + draw(new Line2D.Float(x1, y1, x2, y2)); + } + + @Override + public void drawOval(int x, int y, int width, int height) { + if (stroke instanceof BasicStroke && ((BasicStroke)stroke).getLineWidth() <= 1 && + ((BasicStroke)stroke).getDashArray() == null && + (transform.isIdentity() || transform.getType() == AffineTransform.TYPE_TRANSLATION)) { + Point p = new Point(x, y); + transform.transform(p, p); + MultiRectArea mra = JavaArcRasterizer.rasterize(x, y, width, height, 0, 360, clip); + fillMultiRectArea(mra); + return; + } + draw(new Ellipse2D.Float(x, y, width, height)); + } + + @Override + public void drawPolygon(int[] xpoints, int[] ypoints, int npoints) { + draw(new Polygon(xpoints, ypoints, npoints)); + } + + @Override + public void drawPolygon(Polygon polygon) { + draw(polygon); + } + + @Override + public void drawPolyline(int[] xpoints, int[] ypoints, int npoints) { + for (int i = 0; i < npoints-1; i++) { + drawLine(xpoints[i], ypoints[i], xpoints[i+1], ypoints[i+1]); + } + } + + @Override + public void drawRenderableImage(RenderableImage img, AffineTransform xform) { + if (img == null) { + return; + } + + double scaleX = xform.getScaleX(); + double scaleY = xform.getScaleY(); + if (scaleX == 1 && scaleY == 1) { + drawRenderedImage(img.createDefaultRendering(), xform); + } else { + int width = (int)Math.round(img.getWidth()*scaleX); + int height = (int)Math.round(img.getHeight()*scaleY); + xform = (AffineTransform)xform.clone(); + xform.scale(1, 1); + drawRenderedImage(img.createScaledRendering(width, height, null), xform); + } + } + + @Override + public void drawRenderedImage(RenderedImage rimg, AffineTransform xform) { + if (rimg == null) { + return; + } + + Image img = null; + + if (rimg instanceof Image) { + img = (Image)rimg; + } else { + //TODO: Create new class to provide Image interface for RenderedImage or rewrite this method + img = new BufferedImage(rimg.getColorModel(), rimg.copyData(null), false, null); + } + + drawImage(img, xform, null); + } + + @Override + public void drawRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight) { + if (debugOutput) { + System.err.println("CommonGraphics2D.drawRoundRect("+x+", "+y+", "+width+", "+height+","+arcWidth+", "+arcHeight+")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ + } + + draw(new RoundRectangle2D.Float(x, y, width, height, arcWidth, arcHeight)); + } + + + + + + /*************************************************************************** + * + * String methods + * + ***************************************************************************/ + + @Override + public void drawString(AttributedCharacterIterator iterator, float x, float y) { + GlyphVector gv = font.createGlyphVector(frc, iterator); + drawGlyphVector(gv, x, y); + } + + @Override + public void drawString(AttributedCharacterIterator iterator, int x, int y) { + drawString(iterator, (float)x, (float)y); + } + + @Override + public void drawString(String str, int x, int y) { + drawString(str, (float)x, (float)y); + } + + @Override + public void drawString(String str, float x, float y) { + if (debugOutput) { + System.err.println("CommonGraphics2D.drawString("+str+", "+x+", "+y+")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ + } + + AffineTransform at = (AffineTransform)this.getTransform().clone(); + AffineTransform fontTransform = font.getTransform(); + at.concatenate(fontTransform); + + double[] matrix = new double[6]; + if (!at.isIdentity()){ + + int atType = at.getType(); + at.getMatrix(matrix); + + // TYPE_TRANSLATION + if (atType == AffineTransform.TYPE_TRANSLATION){ + jtr.drawString(this, str, + (float)(x+fontTransform.getTranslateX()), + (float)(y+fontTransform.getTranslateY())); + return; + } + // TODO: we use slow type of drawing strings when Font object + // in Graphics has transforms, we just fill outlines. New textrenderer + // is to be implemented. + Shape sh = font.createGlyphVector(this.getFontRenderContext(), str).getOutline(x, y); + this.fill(sh); + + } else { + jtr.drawString(this, str, x, y); + } + + } + + @Override + public void drawGlyphVector(GlyphVector gv, float x, float y) { + + AffineTransform at = gv.getFont().getTransform(); + + double[] matrix = new double[6]; + if ((at != null) && (!at.isIdentity())){ + + int atType = at.getType(); + at.getMatrix(matrix); + + // TYPE_TRANSLATION + if ((atType == AffineTransform.TYPE_TRANSLATION) && + ((gv.getLayoutFlags() & GlyphVector.FLAG_HAS_TRANSFORMS) == 0)){ + jtr.drawGlyphVector(this, gv, (int)(x+matrix[4]), (int)(y+matrix[5])); + return; + } + } else { + if (((gv.getLayoutFlags() & GlyphVector.FLAG_HAS_TRANSFORMS) == 0)){ + jtr.drawGlyphVector(this, gv, x, y); + return; + } + } + + // TODO: we use slow type of drawing strings when Font object + // in Graphics has transforms, we just fill outlines. New textrenderer + // is to be implemented. + + Shape sh = gv.getOutline(x, y); + this.fill(sh); + + } + + + + + /*************************************************************************** + * + * Fill methods + * + ***************************************************************************/ + + @Override + public void fill(Shape s) { + s = transform.createTransformedShape(s); + MultiRectArea mra = jsr.rasterize(s, 0.5); + fillMultiRectArea(mra); + } + + @Override + public void fillArc(int x, int y, int width, int height, int sa, int ea) { + fill(new Arc2D.Float(x, y, width, height, sa, ea, Arc2D.PIE)); + } + + @Override + public void fillOval(int x, int y, int width, int height) { + fill(new Ellipse2D.Float(x, y, width, height)); + } + + @Override + public void fillPolygon(int[] xpoints, int[] ypoints, int npoints) { + fill(new Polygon(xpoints, ypoints, npoints)); + } + + @Override + public void fillPolygon(Polygon polygon) { + fill(polygon); + } + + @Override + public void fillRect(int x, int y, int width, int height) { + if (debugOutput) { + System.err.println("CommonGraphics2D.fillRect("+x+", "+y+", "+width+", "+height+")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ + } + + fill(new Rectangle(x, y, width, height)); + } + + @Override + public void fillRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight) { + if (debugOutput) { + System.err.println("CommonGraphics2D.fillRoundRect("+x+", "+y+", "+width+", "+height+","+arcWidth+", "+arcHeight+")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ + } + + fill(new RoundRectangle2D.Float(x, y, width, height, arcWidth, arcHeight)); + } + + + + + /*************************************************************************** + * + * Get methods + * + ***************************************************************************/ + + @Override + public Color getBackground() { + return bgColor; + } + + @Override + public Shape getClip() { + if (clip == null) { + return null; + } + + MultiRectArea res = new MultiRectArea(clip); + res.translate(-Math.round((float)transform.getTranslateX()), -Math.round((float)transform.getTranslateY())); + return res; + } + + @Override + public Rectangle getClipBounds() { + if (clip == null) { + return null; + } + + Rectangle res = (Rectangle) clip.getBounds().clone(); + res.translate(-Math.round((float)transform.getTranslateX()), -Math.round((float)transform.getTranslateY())); + return res; + } + + @Override + public Color getColor() { + return fgColor; + } + + @Override + public Composite getComposite() { + return composite; + } + + @Override + public Font getFont() { + return font; + } + + @SuppressWarnings("deprecation") + @Override + public FontMetrics getFontMetrics(Font font) { + return Toolkit.getDefaultToolkit().getFontMetrics(font); + } + + @Override + public FontRenderContext getFontRenderContext() { + return frc; + } + + @Override + public Paint getPaint() { + return paint; + } + + @Override + public Object getRenderingHint(RenderingHints.Key key) { + return hints.get(key); + } + + @Override + public RenderingHints getRenderingHints() { + return hints; + } + + @Override + public Stroke getStroke() { + return stroke; + } + + @Override + public AffineTransform getTransform() { + return (AffineTransform)transform.clone(); + } + + @Override + public boolean hit(Rectangle rect, Shape s, boolean onStroke) { + //TODO: Implement method.... + return false; + } + + + + + /*************************************************************************** + * + * Transformation methods + * + ***************************************************************************/ + + @Override + public void rotate(double theta) { + transform.rotate(theta); + transform.getMatrix(matrix); + } + + @Override + public void rotate(double theta, double x, double y) { + transform.rotate(theta, x, y); + transform.getMatrix(matrix); + } + + @Override + public void scale(double sx, double sy) { + transform.scale(sx, sy); + transform.getMatrix(matrix); + } + + @Override + public void shear(double shx, double shy) { + transform.shear(shx, shy); + transform.getMatrix(matrix); + } + + @Override + public void transform(AffineTransform at) { + transform.concatenate(at); + transform.getMatrix(matrix); + } + + @Override + public void translate(double tx, double ty) { + if (debugOutput) { + System.err.println("CommonGraphics2D.translate("+tx+", "+ty+")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + + transform.translate(tx, ty); + transform.getMatrix(matrix); + } + + @Override + public void translate(int tx, int ty) { + if (debugOutput) { + System.err.println("CommonGraphics2D.translate("+tx+", "+ty+")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + + transform.translate(tx, ty); + transform.getMatrix(matrix); + } + + + + + /*************************************************************************** + * + * Set methods + * + ***************************************************************************/ + + @Override + public void setBackground(Color color) { + bgColor = color; + } + + @Override + public void setClip(int x, int y, int width, int height) { + setClip(new Rectangle(x, y, width, height)); + } + + @Override + public void setClip(Shape s) { + if (s == null) { + setTransformedClip(null); + if (debugOutput) { + System.err.println("CommonGraphics2D.setClip(null)"); //$NON-NLS-1$ + } + return; + } + + if (debugOutput) { + System.err.println("CommonGraphics2D.setClip("+s.getBounds()+")"); //$NON-NLS-1$ //$NON-NLS-2$ + } + + if (s instanceof MultiRectArea) { + MultiRectArea nclip = new MultiRectArea((MultiRectArea)s); + nclip.translate(Math.round((float)transform.getTranslateX()), Math.round((float)transform.getTranslateY())); + setTransformedClip(nclip); + } else { + int type = transform.getType(); + if(s instanceof Rectangle && (type & (AffineTransform.TYPE_IDENTITY | + AffineTransform.TYPE_TRANSLATION)) != 0){ + MultiRectArea nclip = new MultiRectArea((Rectangle)s); + if(type == AffineTransform.TYPE_TRANSLATION){ + nclip.translate((int)transform.getTranslateX(), (int)transform.getTranslateY()); + } + setTransformedClip(nclip); + } else { + s = transform.createTransformedShape(s); + setTransformedClip(jsr.rasterize(s, 0.5)); + } + } + } + + @Override + public void setColor(Color color) { + if (color != null) { + fgColor = color; + paint = color; + } + } + + @Override + public void setComposite(Composite composite) { + this.composite = composite; + } + + @Override + public void setFont(Font font) { + this.font = font; + } + + @Override + public void setPaint(Paint paint) { + if (paint == null) + return; + + this.paint = paint; + if (paint instanceof Color) { + fgColor = (Color)paint; + } + } + + @Override + public void setPaintMode() { + composite = AlphaComposite.SrcOver; + } + + @Override + public void setRenderingHint(RenderingHints.Key key, Object value) { + hints.put(key, value); + } + + @Override + public void setRenderingHints(Map hints) { + this.hints.clear(); + this.hints.putAll(hints); + } + + @Override + public void setStroke(Stroke stroke) { + this.stroke = stroke; + } + + @Override + public void setTransform(AffineTransform transform) { + this.transform = transform; + + transform.getMatrix(matrix); + } + + @Override + public void setXORMode(Color color) { + composite = new XORComposite(color); + } + + + // Protected methods + protected void setTransformedClip(MultiRectArea clip) { + this.clip = clip; + } + + /** + * This method fills the given MultiRectArea with current paint. + * It calls fillMultiRectAreaColor and fillMultiRectAreaPaint + * methods depending on the type of current paint. + * @param mra MultiRectArea to fill + */ + protected void fillMultiRectArea(MultiRectArea mra) { + if (clip != null) { + mra.intersect(clip); + } + + // Return if all stuff is clipped + if (mra.rect[0] < 5) { + return; + } + + if (debugOutput) { + System.err.println("CommonGraphics2D.fillMultiRectArea("+mra+")"); //$NON-NLS-1$ //$NON-NLS-2$ + } + + if (paint instanceof Color){ + fillMultiRectAreaColor(mra); + }else{ + fillMultiRectAreaPaint(mra); + } + } + + /** + * This method fills the given MultiRectArea with solid color. + * @param mra MultiRectArea to fill + */ + protected void fillMultiRectAreaColor(MultiRectArea mra) { + fillMultiRectAreaPaint(mra); + } + + /** + * This method fills the given MultiRectArea with any paint. + * @param mra MultiRectArea to fill + */ + protected void fillMultiRectAreaPaint(MultiRectArea mra) { + Rectangle rec = mra.getBounds(); + int x = rec.x; + int y = rec.y; + int w = rec.width; + int h = rec.height; + if(w <= 0 || h <= 0) { + return; + } + PaintContext pc = paint.createContext(null, rec, rec, transform, hints); + Raster r = pc.getRaster(x, y, w, h); + WritableRaster wr; + if(r instanceof WritableRaster){ + wr = (WritableRaster) r; + }else{ + wr = r.createCompatibleWritableRaster(); + wr.setRect(r); + } + Surface srcSurf = new ImageSurface(pc.getColorModel(), wr); + blitter.blit(0, 0, srcSurf, x, y, dstSurf, w, h, + composite, null, mra); + srcSurf.dispose(); + } + + /** + * Copies graphics class fields. + * Used in create method + * + * @param copy Graphics class to copy + */ + protected void copyInternalFields(CommonGraphics2D copy) { + if (clip == null) { + copy.setTransformedClip(null); + } else { + copy.setTransformedClip(new MultiRectArea(clip)); + } + copy.setBackground(bgColor); + copy.setColor(fgColor); + copy.setPaint(paint); + copy.setComposite(composite); + copy.setStroke(stroke); + copy.setFont(font); + copy.setTransform(new AffineTransform(transform)); + //copy.origTransform = new AffineTransform(origTransform); + copy.origPoint = new Point(origPoint); + } +} \ No newline at end of file diff --git a/awt/org/apache/harmony/awt/gl/CommonGraphics2DFactory.java b/awt/org/apache/harmony/awt/gl/CommonGraphics2DFactory.java new file mode 100644 index 0000000..27e3ef0 --- /dev/null +++ b/awt/org/apache/harmony/awt/gl/CommonGraphics2DFactory.java @@ -0,0 +1,78 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Alexey A. Petrenko, Ilya S. Okomin + * @version $Revision$ + */ +package org.apache.harmony.awt.gl; + +import java.awt.Font; +import java.awt.FontMetrics; +import java.awt.peer.FontPeer; + +import org.apache.harmony.awt.gl.font.FontMetricsImpl; +import org.apache.harmony.awt.wtk.GraphicsFactory; + +/** + * Common GraphicsFactory implementation + * + */ +public abstract class CommonGraphics2DFactory implements GraphicsFactory { + + // static instance of CommonGraphics2DFactory + public static CommonGraphics2DFactory inst; + + /** + * Returns FontMetrics object that keeps metrics of the specified font. + * + * @param font specified Font + * @return FontMetrics object corresponding to the specified Font object + */ + public FontMetrics getFontMetrics(Font font) { + FontMetrics fm; + for (FontMetrics element : cacheFM) { + fm = element; + if (fm == null){ + break; + } + + if (fm.getFont().equals(font)){ + return fm; + } + } + fm = new FontMetricsImpl(font); + + System.arraycopy(cacheFM, 0, cacheFM, 1, cacheFM.length -1); + cacheFM[0] = fm; + + return fm; + } + // Font methods + + public FontPeer getFontPeer(Font font) { + return getFontManager().getFontPeer(font.getName(), font.getStyle(), font.getSize()); + } + + /** + * Embeds font from gile with specified path into the system. + * + * @param fontFilePath path to the font file + * @return Font object that was created from the file. + */ + public abstract Font embedFont(String fontFilePath); + +} \ No newline at end of file diff --git a/awt/org/apache/harmony/awt/gl/CommonGraphicsEnvironment.java b/awt/org/apache/harmony/awt/gl/CommonGraphicsEnvironment.java new file mode 100644 index 0000000..5c78e50 --- /dev/null +++ b/awt/org/apache/harmony/awt/gl/CommonGraphicsEnvironment.java @@ -0,0 +1,67 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Alexey A. Petrenko, Oleg V. Khaschansky + * @version $Revision$ + */ +package org.apache.harmony.awt.gl; + +import java.awt.Font; +import java.awt.Graphics2D; +import java.awt.GraphicsEnvironment; +import java.awt.image.BufferedImage; +import java.util.ArrayList; +import java.util.Locale; + +import org.apache.harmony.awt.gl.image.BufferedImageGraphics2D; + +/** + * Common GraphicsEnvironment implementation + * + */ +public abstract class CommonGraphicsEnvironment extends GraphicsEnvironment { + + @Override + public Graphics2D createGraphics(BufferedImage bufferedImage) { + return new BufferedImageGraphics2D(bufferedImage); + } + + @Override + public String[] getAvailableFontFamilyNames(Locale locale) { + Font[] fonts = getAllFonts(); + ArrayList familyNames = new ArrayList(); + + for (Font element : fonts) { + String name = element.getFamily(locale); + if (!familyNames.contains(name)) { + familyNames.add(name); + } + } + + return familyNames.toArray(new String[familyNames.size()]); + } + + @Override + public Font[] getAllFonts() { + return CommonGraphics2DFactory.inst.getFontManager().getAllFonts(); + } + + @Override + public String[] getAvailableFontFamilyNames() { + return CommonGraphics2DFactory.inst.getFontManager().getAllFamilies(); + } +} diff --git a/awt/org/apache/harmony/awt/gl/Crossing.java b/awt/org/apache/harmony/awt/gl/Crossing.java new file mode 100644 index 0000000..ae7fb0e --- /dev/null +++ b/awt/org/apache/harmony/awt/gl/Crossing.java @@ -0,0 +1,889 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Denis M. Kishenko + * @version $Revision$ + */ +package org.apache.harmony.awt.gl; + +import java.awt.Shape; +import java.awt.geom.PathIterator; + +public class Crossing { + + /** + * Allowable tolerance for bounds comparison + */ + static final double DELTA = 1E-5; + + /** + * If roots have distance less then ROOT_DELTA they are double + */ + static final double ROOT_DELTA = 1E-10; + + /** + * Rectangle cross segment + */ + public static final int CROSSING = 255; + + /** + * Unknown crossing result + */ + static final int UNKNOWN = 254; + + /** + * Solves quadratic equation + * @param eqn - the coefficients of the equation + * @param res - the roots of the equation + * @return a number of roots + */ + public static int solveQuad(double eqn[], double res[]) { + double a = eqn[2]; + double b = eqn[1]; + double c = eqn[0]; + int rc = 0; + if (a == 0.0) { + if (b == 0.0) { + return -1; + } + res[rc++] = -c / b; + } else { + double d = b * b - 4.0 * a * c; + // d < 0.0 + if (d < 0.0) { + return 0; + } + d = Math.sqrt(d); + res[rc++] = (- b + d) / (a * 2.0); + // d != 0.0 + if (d != 0.0) { + res[rc++] = (- b - d) / (a * 2.0); + } + } + return fixRoots(res, rc); + } + + /** + * Solves cubic equation + * @param eqn - the coefficients of the equation + * @param res - the roots of the equation + * @return a number of roots + */ + public static int solveCubic(double eqn[], double res[]) { + double d = eqn[3]; + if (d == 0) { + return solveQuad(eqn, res); + } + double a = eqn[2] / d; + double b = eqn[1] / d; + double c = eqn[0] / d; + int rc = 0; + + double Q = (a * a - 3.0 * b) / 9.0; + double R = (2.0 * a * a * a - 9.0 * a * b + 27.0 * c) / 54.0; + double Q3 = Q * Q * Q; + double R2 = R * R; + double n = - a / 3.0; + + if (R2 < Q3) { + double t = Math.acos(R / Math.sqrt(Q3)) / 3.0; + double p = 2.0 * Math.PI / 3.0; + double m = -2.0 * Math.sqrt(Q); + res[rc++] = m * Math.cos(t) + n; + res[rc++] = m * Math.cos(t + p) + n; + res[rc++] = m * Math.cos(t - p) + n; + } else { +// Debug.println("R2 >= Q3 (" + R2 + "/" + Q3 + ")"); + double A = Math.pow(Math.abs(R) + Math.sqrt(R2 - Q3), 1.0 / 3.0); + if (R > 0.0) { + A = -A; + } +// if (A == 0.0) { + if (-ROOT_DELTA < A && A < ROOT_DELTA) { + res[rc++] = n; + } else { + double B = Q / A; + res[rc++] = A + B + n; +// if (R2 == Q3) { + double delta = R2 - Q3; + if (-ROOT_DELTA < delta && delta < ROOT_DELTA) { + res[rc++] = - (A + B) / 2.0 + n; + } + } + + } + return fixRoots(res, rc); + } + + /** + * Excludes double roots. Roots are double if they lies enough close with each other. + * @param res - the roots + * @param rc - the roots count + * @return new roots count + */ + static int fixRoots(double res[], int rc) { + int tc = 0; + for(int i = 0; i < rc; i++) { + out: { + for(int j = i + 1; j < rc; j++) { + if (isZero(res[i] - res[j])) { + break out; + } + } + res[tc++] = res[i]; + } + } + return tc; + } + + /** + * QuadCurve class provides basic functionality to find curve crossing and calculating bounds + */ + public static class QuadCurve { + + double ax, ay, bx, by; + double Ax, Ay, Bx, By; + + public QuadCurve(double x1, double y1, double cx, double cy, double x2, double y2) { + ax = x2 - x1; + ay = y2 - y1; + bx = cx - x1; + by = cy - y1; + + Bx = bx + bx; // Bx = 2.0 * bx + Ax = ax - Bx; // Ax = ax - 2.0 * bx + + By = by + by; // By = 2.0 * by + Ay = ay - By; // Ay = ay - 2.0 * by + } + + int cross(double res[], int rc, double py1, double py2) { + int cross = 0; + + for (int i = 0; i < rc; i++) { + double t = res[i]; + + // CURVE-OUTSIDE + if (t < -DELTA || t > 1 + DELTA) { + continue; + } + // CURVE-START + if (t < DELTA) { + if (py1 < 0.0 && (bx != 0.0 ? bx : ax - bx) < 0.0) { + cross--; + } + continue; + } + // CURVE-END + if (t > 1 - DELTA) { + if (py1 < ay && (ax != bx ? ax - bx : bx) > 0.0) { + cross++; + } + continue; + } + // CURVE-INSIDE + double ry = t * (t * Ay + By); + // ry = t * t * Ay + t * By + if (ry > py2) { + double rxt = t * Ax + bx; + // rxt = 2.0 * t * Ax + Bx = 2.0 * t * Ax + 2.0 * bx + if (rxt > -DELTA && rxt < DELTA) { + continue; + } + cross += rxt > 0.0 ? 1 : -1; + } + } // for + + return cross; + } + + int solvePoint(double res[], double px) { + double eqn[] = {-px, Bx, Ax}; + return solveQuad(eqn, res); + } + + int solveExtrem(double res[]) { + int rc = 0; + if (Ax != 0.0) { + res[rc++] = - Bx / (Ax + Ax); + } + if (Ay != 0.0) { + res[rc++] = - By / (Ay + Ay); + } + return rc; + } + + int addBound(double bound[], int bc, double res[], int rc, double minX, double maxX, boolean changeId, int id) { + for(int i = 0; i < rc; i++) { + double t = res[i]; + if (t > -DELTA && t < 1 + DELTA) { + double rx = t * (t * Ax + Bx); + if (minX <= rx && rx <= maxX) { + bound[bc++] = t; + bound[bc++] = rx; + bound[bc++] = t * (t * Ay + By); + bound[bc++] = id; + if (changeId) { + id++; + } + } + } + } + return bc; + } + + } + + /** + * CubicCurve class provides basic functionality to find curve crossing and calculating bounds + */ + public static class CubicCurve { + + double ax, ay, bx, by, cx, cy; + double Ax, Ay, Bx, By, Cx, Cy; + double Ax3, Bx2; + + public CubicCurve(double x1, double y1, double cx1, double cy1, double cx2, double cy2, double x2, double y2) { + ax = x2 - x1; + ay = y2 - y1; + bx = cx1 - x1; + by = cy1 - y1; + cx = cx2 - x1; + cy = cy2 - y1; + + Cx = bx + bx + bx; // Cx = 3.0 * bx + Bx = cx + cx + cx - Cx - Cx; // Bx = 3.0 * cx - 6.0 * bx + Ax = ax - Bx - Cx; // Ax = ax - 3.0 * cx + 3.0 * bx + + Cy = by + by + by; // Cy = 3.0 * by + By = cy + cy + cy - Cy - Cy; // By = 3.0 * cy - 6.0 * by + Ay = ay - By - Cy; // Ay = ay - 3.0 * cy + 3.0 * by + + Ax3 = Ax + Ax + Ax; + Bx2 = Bx + Bx; + } + + int cross(double res[], int rc, double py1, double py2) { + int cross = 0; + for (int i = 0; i < rc; i++) { + double t = res[i]; + + // CURVE-OUTSIDE + if (t < -DELTA || t > 1 + DELTA) { + continue; + } + // CURVE-START + if (t < DELTA) { + if (py1 < 0.0 && (bx != 0.0 ? bx : (cx != bx ? cx - bx : ax - cx)) < 0.0) { + cross--; + } + continue; + } + // CURVE-END + if (t > 1 - DELTA) { + if (py1 < ay && (ax != cx ? ax - cx : (cx != bx ? cx - bx : bx)) > 0.0) { + cross++; + } + continue; + } + // CURVE-INSIDE + double ry = t * (t * (t * Ay + By) + Cy); + // ry = t * t * t * Ay + t * t * By + t * Cy + if (ry > py2) { + double rxt = t * (t * Ax3 + Bx2) + Cx; + // rxt = 3.0 * t * t * Ax + 2.0 * t * Bx + Cx + if (rxt > -DELTA && rxt < DELTA) { + rxt = t * (Ax3 + Ax3) + Bx2; + // rxt = 6.0 * t * Ax + 2.0 * Bx + if (rxt < -DELTA || rxt > DELTA) { + // Inflection point + continue; + } + rxt = ax; + } + cross += rxt > 0.0 ? 1 : -1; + } + } //for + + return cross; + } + + int solvePoint(double res[], double px) { + double eqn[] = {-px, Cx, Bx, Ax}; + return solveCubic(eqn, res); + } + + int solveExtremX(double res[]) { + double eqn[] = {Cx, Bx2, Ax3}; + return solveQuad(eqn, res); + } + + int solveExtremY(double res[]) { + double eqn[] = {Cy, By + By, Ay + Ay + Ay}; + return solveQuad(eqn, res); + } + + int addBound(double bound[], int bc, double res[], int rc, double minX, double maxX, boolean changeId, int id) { + for(int i = 0; i < rc; i++) { + double t = res[i]; + if (t > -DELTA && t < 1 + DELTA) { + double rx = t * (t * (t * Ax + Bx) + Cx); + if (minX <= rx && rx <= maxX) { + bound[bc++] = t; + bound[bc++] = rx; + bound[bc++] = t * (t * (t * Ay + By) + Cy); + bound[bc++] = id; + if (changeId) { + id++; + } + } + } + } + return bc; + } + + } + + /** + * Returns how many times ray from point (x,y) cross line. + */ + public static int crossLine(double x1, double y1, double x2, double y2, double x, double y) { + + // LEFT/RIGHT/UP/EMPTY + if ((x < x1 && x < x2) || + (x > x1 && x > x2) || + (y > y1 && y > y2) || + (x1 == x2)) + { + return 0; + } + + // DOWN + if (y < y1 && y < y2) { + } else { + // INSIDE + if ((y2 - y1) * (x - x1) / (x2 - x1) <= y - y1) { + // INSIDE-UP + return 0; + } + } + + // START + if (x == x1) { + return x1 < x2 ? 0 : -1; + } + + // END + if (x == x2) { + return x1 < x2 ? 1 : 0; + } + + // INSIDE-DOWN + return x1 < x2 ? 1 : -1; + } + + /** + * Returns how many times ray from point (x,y) cross quard curve + */ + public static int crossQuad(double x1, double y1, double cx, double cy, double x2, double y2, double x, double y) { + + // LEFT/RIGHT/UP/EMPTY + if ((x < x1 && x < cx && x < x2) || + (x > x1 && x > cx && x > x2) || + (y > y1 && y > cy && y > y2) || + (x1 == cx && cx == x2)) + { + return 0; + } + + // DOWN + if (y < y1 && y < cy && y < y2 && x != x1 && x != x2) { + if (x1 < x2) { + return x1 < x && x < x2 ? 1 : 0; + } + return x2 < x && x < x1 ? -1 : 0; + } + + // INSIDE + QuadCurve c = new QuadCurve(x1, y1, cx, cy, x2, y2); + double px = x - x1; + double py = y - y1; + double res[] = new double[3]; + int rc = c.solvePoint(res, px); + + return c.cross(res, rc, py, py); + } + + /** + * Returns how many times ray from point (x,y) cross cubic curve + */ + public static int crossCubic(double x1, double y1, double cx1, double cy1, double cx2, double cy2, double x2, double y2, double x, double y) { + + // LEFT/RIGHT/UP/EMPTY + if ((x < x1 && x < cx1 && x < cx2 && x < x2) || + (x > x1 && x > cx1 && x > cx2 && x > x2) || + (y > y1 && y > cy1 && y > cy2 && y > y2) || + (x1 == cx1 && cx1 == cx2 && cx2 == x2)) + { + return 0; + } + + // DOWN + if (y < y1 && y < cy1 && y < cy2 && y < y2 && x != x1 && x != x2) { + if (x1 < x2) { + return x1 < x && x < x2 ? 1 : 0; + } + return x2 < x && x < x1 ? -1 : 0; + } + + // INSIDE + CubicCurve c = new CubicCurve(x1, y1, cx1, cy1, cx2, cy2, x2, y2); + double px = x - x1; + double py = y - y1; + double res[] = new double[3]; + int rc = c.solvePoint(res, px); + return c.cross(res, rc, py, py); + } + + /** + * Returns how many times ray from point (x,y) cross path + */ + public static int crossPath(PathIterator p, double x, double y) { + int cross = 0; + double mx, my, cx, cy; + mx = my = cx = cy = 0.0; + double coords[] = new double[6]; + + while (!p.isDone()) { + switch (p.currentSegment(coords)) { + case PathIterator.SEG_MOVETO: + if (cx != mx || cy != my) { + cross += crossLine(cx, cy, mx, my, x, y); + } + mx = cx = coords[0]; + my = cy = coords[1]; + break; + case PathIterator.SEG_LINETO: + cross += crossLine(cx, cy, cx = coords[0], cy = coords[1], x, y); + break; + case PathIterator.SEG_QUADTO: + cross += crossQuad(cx, cy, coords[0], coords[1], cx = coords[2], cy = coords[3], x, y); + break; + case PathIterator.SEG_CUBICTO: + cross += crossCubic(cx, cy, coords[0], coords[1], coords[2], coords[3], cx = coords[4], cy = coords[5], x, y); + break; + case PathIterator.SEG_CLOSE: + if (cy != my || cx != mx) { + cross += crossLine(cx, cy, cx = mx, cy = my, x, y); + } + break; + } + p.next(); + } + if (cy != my) { + cross += crossLine(cx, cy, mx, my, x, y); + } + return cross; + } + + /** + * Returns how many times ray from point (x,y) cross shape + */ + public static int crossShape(Shape s, double x, double y) { + if (!s.getBounds2D().contains(x, y)) { + return 0; + } + return crossPath(s.getPathIterator(null), x, y); + } + + /** + * Returns true if value enough small + */ + public static boolean isZero(double val) { + return -DELTA < val && val < DELTA; + } + + /** + * Sort bound array + */ + static void sortBound(double bound[], int bc) { + for(int i = 0; i < bc - 4; i += 4) { + int k = i; + for(int j = i + 4; j < bc; j += 4) { + if (bound[k] > bound[j]) { + k = j; + } + } + if (k != i) { + double tmp = bound[i]; + bound[i] = bound[k]; + bound[k] = tmp; + tmp = bound[i + 1]; + bound[i + 1] = bound[k + 1]; + bound[k + 1] = tmp; + tmp = bound[i + 2]; + bound[i + 2] = bound[k + 2]; + bound[k + 2] = tmp; + tmp = bound[i + 3]; + bound[i + 3] = bound[k + 3]; + bound[k + 3] = tmp; + } + } + } + + /** + * Returns are bounds intersect or not intersect rectangle + */ + static int crossBound(double bound[], int bc, double py1, double py2) { + + // LEFT/RIGHT + if (bc == 0) { + return 0; + } + + // Check Y coordinate + int up = 0; + int down = 0; + for(int i = 2; i < bc; i += 4) { + if (bound[i] < py1) { + up++; + continue; + } + if (bound[i] > py2) { + down++; + continue; + } + return CROSSING; + } + + // UP + if (down == 0) { + return 0; + } + + if (up != 0) { + // bc >= 2 + sortBound(bound, bc); + boolean sign = bound[2] > py2; + for(int i = 6; i < bc; i += 4) { + boolean sign2 = bound[i] > py2; + if (sign != sign2 && bound[i + 1] != bound[i - 3]) { + return CROSSING; + } + sign = sign2; + } + } + return UNKNOWN; + } + + /** + * Returns how many times rectangle stripe cross line or the are intersect + */ + public static int intersectLine(double x1, double y1, double x2, double y2, double rx1, double ry1, double rx2, double ry2) { + + // LEFT/RIGHT/UP + if ((rx2 < x1 && rx2 < x2) || + (rx1 > x1 && rx1 > x2) || + (ry1 > y1 && ry1 > y2)) + { + return 0; + } + + // DOWN + if (ry2 < y1 && ry2 < y2) { + } else { + + // INSIDE + if (x1 == x2) { + return CROSSING; + } + + // Build bound + double bx1, bx2; + if (x1 < x2) { + bx1 = x1 < rx1 ? rx1 : x1; + bx2 = x2 < rx2 ? x2 : rx2; + } else { + bx1 = x2 < rx1 ? rx1 : x2; + bx2 = x1 < rx2 ? x1 : rx2; + } + double k = (y2 - y1) / (x2 - x1); + double by1 = k * (bx1 - x1) + y1; + double by2 = k * (bx2 - x1) + y1; + + // BOUND-UP + if (by1 < ry1 && by2 < ry1) { + return 0; + } + + // BOUND-DOWN + if (by1 > ry2 && by2 > ry2) { + } else { + return CROSSING; + } + } + + // EMPTY + if (x1 == x2) { + return 0; + } + + // CURVE-START + if (rx1 == x1) { + return x1 < x2 ? 0 : -1; + } + + // CURVE-END + if (rx1 == x2) { + return x1 < x2 ? 1 : 0; + } + + if (x1 < x2) { + return x1 < rx1 && rx1 < x2 ? 1 : 0; + } + return x2 < rx1 && rx1 < x1 ? -1 : 0; + + } + + /** + * Returns how many times rectangle stripe cross quad curve or the are intersect + */ + public static int intersectQuad(double x1, double y1, double cx, double cy, double x2, double y2, double rx1, double ry1, double rx2, double ry2) { + + // LEFT/RIGHT/UP ------------------------------------------------------ + if ((rx2 < x1 && rx2 < cx && rx2 < x2) || + (rx1 > x1 && rx1 > cx && rx1 > x2) || + (ry1 > y1 && ry1 > cy && ry1 > y2)) + { + return 0; + } + + // DOWN --------------------------------------------------------------- + if (ry2 < y1 && ry2 < cy && ry2 < y2 && rx1 != x1 && rx1 != x2) { + if (x1 < x2) { + return x1 < rx1 && rx1 < x2 ? 1 : 0; + } + return x2 < rx1 && rx1 < x1 ? -1 : 0; + } + + // INSIDE ------------------------------------------------------------- + QuadCurve c = new QuadCurve(x1, y1, cx, cy, x2, y2); + double px1 = rx1 - x1; + double py1 = ry1 - y1; + double px2 = rx2 - x1; + double py2 = ry2 - y1; + + double res1[] = new double[3]; + double res2[] = new double[3]; + int rc1 = c.solvePoint(res1, px1); + int rc2 = c.solvePoint(res2, px2); + + // INSIDE-LEFT/RIGHT + if (rc1 == 0 && rc2 == 0) { + return 0; + } + + // Build bound -------------------------------------------------------- + double minX = px1 - DELTA; + double maxX = px2 + DELTA; + double bound[] = new double[28]; + int bc = 0; + // Add roots + bc = c.addBound(bound, bc, res1, rc1, minX, maxX, false, 0); + bc = c.addBound(bound, bc, res2, rc2, minX, maxX, false, 1); + // Add extremal points` + rc2 = c.solveExtrem(res2); + bc = c.addBound(bound, bc, res2, rc2, minX, maxX, true, 2); + // Add start and end + if (rx1 < x1 && x1 < rx2) { + bound[bc++] = 0.0; + bound[bc++] = 0.0; + bound[bc++] = 0.0; + bound[bc++] = 4; + } + if (rx1 < x2 && x2 < rx2) { + bound[bc++] = 1.0; + bound[bc++] = c.ax; + bound[bc++] = c.ay; + bound[bc++] = 5; + } + // End build bound ---------------------------------------------------- + + int cross = crossBound(bound, bc, py1, py2); + if (cross != UNKNOWN) { + return cross; + } + return c.cross(res1, rc1, py1, py2); + } + + /** + * Returns how many times rectangle stripe cross cubic curve or the are intersect + */ + public static int intersectCubic(double x1, double y1, double cx1, double cy1, double cx2, double cy2, double x2, double y2, double rx1, double ry1, double rx2, double ry2) { + + // LEFT/RIGHT/UP + if ((rx2 < x1 && rx2 < cx1 && rx2 < cx2 && rx2 < x2) || + (rx1 > x1 && rx1 > cx1 && rx1 > cx2 && rx1 > x2) || + (ry1 > y1 && ry1 > cy1 && ry1 > cy2 && ry1 > y2)) + { + return 0; + } + + // DOWN + if (ry2 < y1 && ry2 < cy1 && ry2 < cy2 && ry2 < y2 && rx1 != x1 && rx1 != x2) { + if (x1 < x2) { + return x1 < rx1 && rx1 < x2 ? 1 : 0; + } + return x2 < rx1 && rx1 < x1 ? -1 : 0; + } + + // INSIDE + CubicCurve c = new CubicCurve(x1, y1, cx1, cy1, cx2, cy2, x2, y2); + double px1 = rx1 - x1; + double py1 = ry1 - y1; + double px2 = rx2 - x1; + double py2 = ry2 - y1; + + double res1[] = new double[3]; + double res2[] = new double[3]; + int rc1 = c.solvePoint(res1, px1); + int rc2 = c.solvePoint(res2, px2); + + // LEFT/RIGHT + if (rc1 == 0 && rc2 == 0) { + return 0; + } + + double minX = px1 - DELTA; + double maxX = px2 + DELTA; + + // Build bound -------------------------------------------------------- + double bound[] = new double[40]; + int bc = 0; + // Add roots + bc = c.addBound(bound, bc, res1, rc1, minX, maxX, false, 0); + bc = c.addBound(bound, bc, res2, rc2, minX, maxX, false, 1); + // Add extrimal points + rc2 = c.solveExtremX(res2); + bc = c.addBound(bound, bc, res2, rc2, minX, maxX, true, 2); + rc2 = c.solveExtremY(res2); + bc = c.addBound(bound, bc, res2, rc2, minX, maxX, true, 4); + // Add start and end + if (rx1 < x1 && x1 < rx2) { + bound[bc++] = 0.0; + bound[bc++] = 0.0; + bound[bc++] = 0.0; + bound[bc++] = 6; + } + if (rx1 < x2 && x2 < rx2) { + bound[bc++] = 1.0; + bound[bc++] = c.ax; + bound[bc++] = c.ay; + bound[bc++] = 7; + } + // End build bound ---------------------------------------------------- + + int cross = crossBound(bound, bc, py1, py2); + if (cross != UNKNOWN) { + return cross; + } + return c.cross(res1, rc1, py1, py2); + } + + /** + * Returns how many times rectangle stripe cross path or the are intersect + */ + public static int intersectPath(PathIterator p, double x, double y, double w, double h) { + + int cross = 0; + int count; + double mx, my, cx, cy; + mx = my = cx = cy = 0.0; + double coords[] = new double[6]; + + double rx1 = x; + double ry1 = y; + double rx2 = x + w; + double ry2 = y + h; + + while (!p.isDone()) { + count = 0; + switch (p.currentSegment(coords)) { + case PathIterator.SEG_MOVETO: + if (cx != mx || cy != my) { + count = intersectLine(cx, cy, mx, my, rx1, ry1, rx2, ry2); + } + mx = cx = coords[0]; + my = cy = coords[1]; + break; + case PathIterator.SEG_LINETO: + count = intersectLine(cx, cy, cx = coords[0], cy = coords[1], rx1, ry1, rx2, ry2); + break; + case PathIterator.SEG_QUADTO: + count = intersectQuad(cx, cy, coords[0], coords[1], cx = coords[2], cy = coords[3], rx1, ry1, rx2, ry2); + break; + case PathIterator.SEG_CUBICTO: + count = intersectCubic(cx, cy, coords[0], coords[1], coords[2], coords[3], cx = coords[4], cy = coords[5], rx1, ry1, rx2, ry2); + break; + case PathIterator.SEG_CLOSE: + if (cy != my || cx != mx) { + count = intersectLine(cx, cy, mx, my, rx1, ry1, rx2, ry2); + } + cx = mx; + cy = my; + break; + } + if (count == CROSSING) { + return CROSSING; + } + cross += count; + p.next(); + } + if (cy != my) { + count = intersectLine(cx, cy, mx, my, rx1, ry1, rx2, ry2); + if (count == CROSSING) { + return CROSSING; + } + cross += count; + } + return cross; + } + + /** + * Returns how many times rectangle stripe cross shape or the are intersect + */ + public static int intersectShape(Shape s, double x, double y, double w, double h) { + if (!s.getBounds2D().intersects(x, y, w, h)) { + return 0; + } + return intersectPath(s.getPathIterator(null), x, y, w, h); + } + + /** + * Returns true if cross count correspond inside location for non zero path rule + */ + public static boolean isInsideNonZero(int cross) { + return cross != 0; + } + + /** + * Returns true if cross count correspond inside location for even-odd path rule + */ + public static boolean isInsideEvenOdd(int cross) { + return (cross & 1) != 0; + } +} \ No newline at end of file diff --git a/awt/org/apache/harmony/awt/gl/GLVolatileImage.java b/awt/org/apache/harmony/awt/gl/GLVolatileImage.java new file mode 100644 index 0000000..177be23 --- /dev/null +++ b/awt/org/apache/harmony/awt/gl/GLVolatileImage.java @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Igor V. Stolyarov + * @version $Revision$ + */ +package org.apache.harmony.awt.gl; + +import java.awt.image.*; + +import org.apache.harmony.awt.gl.Surface; + +public abstract class GLVolatileImage extends VolatileImage { + + public abstract Surface getImageSurface(); +} diff --git a/awt/org/apache/harmony/awt/gl/ICompositeContext.java b/awt/org/apache/harmony/awt/gl/ICompositeContext.java new file mode 100644 index 0000000..fc5631f --- /dev/null +++ b/awt/org/apache/harmony/awt/gl/ICompositeContext.java @@ -0,0 +1,90 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Igor V. Stolyarov + * @version $Revision$ + */ +package org.apache.harmony.awt.gl; + +import java.awt.Composite; +import java.awt.CompositeContext; +import java.awt.image.ColorModel; +import java.awt.image.Raster; +import java.awt.image.WritableRaster; + +import org.apache.harmony.awt.gl.ImageSurface; +import org.apache.harmony.awt.gl.render.NativeImageBlitter; +import org.apache.harmony.awt.internal.nls.Messages; + + +/** + * This class represent implementation of the CompositeContext interface + */ +public class ICompositeContext implements CompositeContext { + Composite composite; + ColorModel srcCM, dstCM; + ImageSurface srcSurf, dstSurf; + + public ICompositeContext(Composite comp, ColorModel src, ColorModel dst){ + composite = comp; + srcCM = src; + dstCM = dst; + } + + public void dispose() { + srcSurf.dispose(); + dstSurf.dispose(); + } + + public void compose(Raster srcIn, Raster dstIn, WritableRaster dstOut) { + + if(!srcCM.isCompatibleRaster(srcIn)) { + // awt.48=The srcIn raster is incompatible with src ColorModel + throw new IllegalArgumentException(Messages.getString("awt.48")); //$NON-NLS-1$ + } + + if(!dstCM.isCompatibleRaster(dstIn)) { + // awt.49=The dstIn raster is incompatible with dst ColorModel + throw new IllegalArgumentException(Messages.getString("awt.49")); //$NON-NLS-1$ + } + + if(dstIn != dstOut){ + if(!dstCM.isCompatibleRaster(dstOut)) { + // awt.4A=The dstOut raster is incompatible with dst ColorModel + throw new IllegalArgumentException(Messages.getString("awt.4A")); //$NON-NLS-1$ + } + dstOut.setDataElements(0, 0, dstIn); + } + WritableRaster src; + if(srcIn instanceof WritableRaster){ + src = (WritableRaster) srcIn; + }else{ + src = srcIn.createCompatibleWritableRaster(); + src.setDataElements(0, 0, srcIn); + } + srcSurf = new ImageSurface(srcCM, src); + dstSurf = new ImageSurface(dstCM, dstOut); + + int w = Math.min(srcIn.getWidth(), dstOut.getWidth()); + int h = Math.min(srcIn.getHeight(), dstOut.getHeight()); + + NativeImageBlitter.getInstance().blit(0, 0, srcSurf, 0, 0, dstSurf, + w, h, composite, null, null); + + } + +} diff --git a/awt/org/apache/harmony/awt/gl/ImageSurface.java b/awt/org/apache/harmony/awt/gl/ImageSurface.java new file mode 100644 index 0000000..6368dd8 --- /dev/null +++ b/awt/org/apache/harmony/awt/gl/ImageSurface.java @@ -0,0 +1,323 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Igor V. Stolyarov + * @version $Revision$ + * Created on 10.11.2005 + * + */ +package org.apache.harmony.awt.gl; + +import java.awt.color.ColorSpace; +import java.awt.image.BandedSampleModel; +import java.awt.image.BufferedImage; +import java.awt.image.ColorModel; +import java.awt.image.ComponentColorModel; +import java.awt.image.ComponentSampleModel; +import java.awt.image.DirectColorModel; +import java.awt.image.IndexColorModel; +import java.awt.image.MultiPixelPackedSampleModel; +import java.awt.image.PixelInterleavedSampleModel; +import java.awt.image.SampleModel; +import java.awt.image.SinglePixelPackedSampleModel; +import java.awt.image.WritableRaster; + +import org.apache.harmony.awt.gl.color.LUTColorConverter; +import org.apache.harmony.awt.gl.image.DataBufferListener; +import org.apache.harmony.awt.internal.nls.Messages; + + +/** + * This class represent Surface for different types of Images (BufferedImage, + * OffscreenImage and so on) + */ +public class ImageSurface extends Surface implements DataBufferListener { + + boolean nativeDrawable = true; + int surfaceType; + int csType; + ColorModel cm; + WritableRaster raster; + Object data; + + boolean needToRefresh = true; + boolean dataTaken = false; + + private long cachedDataPtr; // Pointer for cached Image Data + private boolean alphaPre; // Cached Image Data alpha premultiplied + + public ImageSurface(ColorModel cm, WritableRaster raster){ + this(cm, raster, Surface.getType(cm, raster)); + } + + public ImageSurface(ColorModel cm, WritableRaster raster, int type){ + if (!cm.isCompatibleRaster(raster)) { + // awt.4D=The raster is incompatible with this ColorModel + throw new IllegalArgumentException(Messages.getString("awt.4D")); //$NON-NLS-1$ + } + this.cm = cm; + this.raster = raster; + surfaceType = type; + + data = AwtImageBackdoorAccessor.getInstance(). + getData(raster.getDataBuffer()); + ColorSpace cs = cm.getColorSpace(); + transparency = cm.getTransparency(); + width = raster.getWidth(); + height = raster.getHeight(); + + // For the moment we can build natively only images which have + // sRGB, Linear_RGB, Linear_Gray Color Space and type different + // from BufferedImage.TYPE_CUSTOM + if(cs == LUTColorConverter.sRGB_CS){ + csType = sRGB_CS; + }else if(cs == LUTColorConverter.LINEAR_RGB_CS){ + csType = Linear_RGB_CS; + }else if(cs == LUTColorConverter.LINEAR_GRAY_CS){ + csType = Linear_Gray_CS; + }else{ + csType = Custom_CS; + nativeDrawable = false; + } + + if(type == BufferedImage.TYPE_CUSTOM){ + nativeDrawable = false; + } + } + + @Override + public ColorModel getColorModel() { + return cm; + } + + @Override + public WritableRaster getRaster() { + return raster; + } + + @Override + public long getSurfaceDataPtr() { + if(surfaceDataPtr == 0L && nativeDrawable){ + createSufaceStructure(); + } + return surfaceDataPtr; + } + + @Override + public Object getData(){ + return data; + } + + @Override + public boolean isNativeDrawable(){ + return nativeDrawable; + } + + @Override + public int getSurfaceType() { + return surfaceType; + } + + /** + * Creates native Surface structure which used for native blitting + */ + private void createSufaceStructure(){ + int cmType = 0; + int numComponents = cm.getNumComponents(); + boolean hasAlpha = cm.hasAlpha(); + boolean isAlphaPre = cm.isAlphaPremultiplied(); + int transparency = cm.getTransparency(); + int bits[] = cm.getComponentSize(); + int pixelStride = cm.getPixelSize(); + int masks[] = null; + int colorMap[] = null; + int colorMapSize = 0; + int transpPixel = -1; + boolean isGrayPallete = false; + SampleModel sm = raster.getSampleModel(); + int smType = 0; + int dataType = sm.getDataType(); + int scanlineStride = 0; + int bankIndeces[] = null; + int bandOffsets[] = null; + int offset = raster.getDataBuffer().getOffset(); + + if(cm instanceof DirectColorModel){ + cmType = DCM; + DirectColorModel dcm = (DirectColorModel) cm; + masks = dcm.getMasks(); + smType = SPPSM; + SinglePixelPackedSampleModel sppsm = (SinglePixelPackedSampleModel) sm; + scanlineStride = sppsm.getScanlineStride(); + + }else if(cm instanceof IndexColorModel){ + cmType = ICM; + IndexColorModel icm = (IndexColorModel) cm; + colorMapSize = icm.getMapSize(); + colorMap = new int[colorMapSize]; + icm.getRGBs(colorMap); + transpPixel = icm.getTransparentPixel(); + isGrayPallete = Surface.isGrayPallete(icm); + + if(sm instanceof MultiPixelPackedSampleModel){ + smType = MPPSM; + MultiPixelPackedSampleModel mppsm = + (MultiPixelPackedSampleModel) sm; + scanlineStride = mppsm.getScanlineStride(); + }else if(sm instanceof ComponentSampleModel){ + smType = CSM; + ComponentSampleModel csm = + (ComponentSampleModel) sm; + scanlineStride = csm.getScanlineStride(); + }else{ + // awt.4D=The raster is incompatible with this ColorModel + throw new IllegalArgumentException(Messages.getString("awt.4D")); //$NON-NLS-1$ + } + + }else if(cm instanceof ComponentColorModel){ + cmType = CCM; + if(sm instanceof ComponentSampleModel){ + ComponentSampleModel csm = (ComponentSampleModel) sm; + scanlineStride = csm.getScanlineStride(); + bankIndeces = csm.getBankIndices(); + bandOffsets = csm.getBandOffsets(); + if(sm instanceof PixelInterleavedSampleModel){ + smType = PISM; + }else if(sm instanceof BandedSampleModel){ + smType = BSM; + }else{ + smType = CSM; + } + }else{ + // awt.4D=The raster is incompatible with this ColorModel + throw new IllegalArgumentException(Messages.getString("awt.4D")); //$NON-NLS-1$ + } + + }else{ + surfaceDataPtr = 0L; + return; + } + surfaceDataPtr = createSurfStruct(surfaceType, width, height, cmType, csType, smType, dataType, + numComponents, pixelStride, scanlineStride, bits, masks, colorMapSize, + colorMap, transpPixel, isGrayPallete, bankIndeces, bandOffsets, + offset, hasAlpha, isAlphaPre, transparency); + } + + @Override + public void dispose() { + if(surfaceDataPtr != 0L){ + dispose(surfaceDataPtr); + surfaceDataPtr = 0L; + } + } + + public long getCachedData(boolean alphaPre){ + if(nativeDrawable){ + if(cachedDataPtr == 0L || needToRefresh || this.alphaPre != alphaPre){ + cachedDataPtr = updateCache(getSurfaceDataPtr(), data, alphaPre); + this.alphaPre = alphaPre; + validate(); + } + } + return cachedDataPtr; + } + + private native long createSurfStruct(int surfaceType, int width, int height, + int cmType, int csType, int smType, int dataType, + int numComponents, int pixelStride, int scanlineStride, + int bits[], int masks[], int colorMapSize, int colorMap[], + int transpPixel, boolean isGrayPalette, int bankIndeces[], + int bandOffsets[], int offset, boolean hasAlpha, boolean isAlphaPre, + int transparency); + + private native void dispose(long structPtr); + + private native void setImageSize(long structPtr, int width, int height); + + private native long updateCache(long structPtr, Object data, boolean alphaPre); + + /** + * Supposes that new raster is compatible with an old one + * @param r + */ + public void setRaster(WritableRaster r) { + raster = r; + data = AwtImageBackdoorAccessor.getInstance().getData(r.getDataBuffer()); + if (surfaceDataPtr != 0) { + setImageSize(surfaceDataPtr, r.getWidth(), r.getHeight()); + } + this.width = r.getWidth(); + this.height = r.getHeight(); + } + + @Override + public long lock() { + // TODO + return 0; + } + + @Override + public void unlock() { + //TODO + } + + @Override + public Surface getImageSurface() { + return this; + } + + public void dataChanged() { + needToRefresh = true; + clearValidCaches(); + } + + public void dataTaken() { + dataTaken = true; + needToRefresh = true; + clearValidCaches(); + } + + public void dataReleased(){ + dataTaken = false; + needToRefresh = true; + clearValidCaches(); + } + + @Override + public void invalidate(){ + needToRefresh = true; + clearValidCaches(); + } + + @Override + public void validate(){ + if(!needToRefresh) { + return; + } + if(!dataTaken){ + needToRefresh = false; + AwtImageBackdoorAccessor ba = AwtImageBackdoorAccessor.getInstance(); + ba.validate(raster.getDataBuffer()); + } + + } + + @Override + public boolean invalidated(){ + return needToRefresh; + } +} diff --git a/awt/org/apache/harmony/awt/gl/MultiRectArea.java b/awt/org/apache/harmony/awt/gl/MultiRectArea.java new file mode 100644 index 0000000..c4267f3 --- /dev/null +++ b/awt/org/apache/harmony/awt/gl/MultiRectArea.java @@ -0,0 +1,836 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Denis M. Kishenko + * @version $Revision$ + */ +package org.apache.harmony.awt.gl; + +import java.awt.Rectangle; +import java.awt.Shape; +import java.awt.geom.AffineTransform; +import java.awt.geom.PathIterator; +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; +import java.util.ArrayList; +import java.util.NoSuchElementException; + +import org.apache.harmony.awt.internal.nls.Messages; + +public class MultiRectArea implements Shape { + + /** + * If CHECK is true validation check active + */ + private static final boolean CHECK = false; + + boolean sorted = true; + + /** + * Rectangle buffer + */ + public int[] rect; + + /** + * Bounding box + */ + Rectangle bounds; + + /** + * Result rectangle array + */ + Rectangle[] rectangles; + + /** + * LineCash provides creating MultiRectArea line by line. Used in JavaShapeRasterizer. + */ + public static class LineCash extends MultiRectArea { + + int lineY; + int bottomCount; + int[] bottom; + + public LineCash(int size) { + super(); + bottom = new int[size]; + bottomCount = 0; + } + + public void setLine(int y) { + lineY = y; + } + + public void skipLine() { + lineY++; + bottomCount = 0; + } + + public void addLine(int[] points, int pointCount) { + int bottomIndex = 0; + int pointIndex = 0; + int rectIndex = 0; + int pointX1 = 0; + int pointX2 = 0; + int bottomX1 = 0; + int bottomX2 = 0; + boolean appendRect = false; + boolean deleteRect = false; + int lastCount = bottomCount; + + while (bottomIndex < lastCount || pointIndex < pointCount) { + + appendRect = false; + deleteRect = false; + + if (bottomIndex < lastCount) { + rectIndex = bottom[bottomIndex]; + bottomX1 = rect[rectIndex]; + bottomX2 = rect[rectIndex + 2]; + } else { + appendRect = true; + } + + if (pointIndex < pointCount) { + pointX1 = points[pointIndex]; + pointX2 = points[pointIndex + 1]; + } else { + deleteRect = true; + } + + if (!deleteRect && !appendRect) { + if (pointX1 == bottomX1 && pointX2 == bottomX2) { + rect[rectIndex + 3] = rect[rectIndex + 3] + 1; + pointIndex += 2; + bottomIndex++; + continue; + } + deleteRect = pointX2 >= bottomX1; + appendRect = pointX1 <= bottomX2; + } + + if (deleteRect) { + if (bottomIndex < bottomCount - 1) { + System.arraycopy(bottom, bottomIndex + 1, bottom, bottomIndex, bottomCount - bottomIndex - 1); + rectIndex -= 4; + } + bottomCount--; + lastCount--; + } + + if (appendRect) { + int i = rect[0]; + bottom[bottomCount++] = i; + rect = MultiRectAreaOp.checkBufSize(rect, 4); + rect[i++] = pointX1; + rect[i++] = lineY; + rect[i++] = pointX2; + rect[i++] = lineY; + pointIndex += 2; + } + } + lineY++; + + invalidate(); + } + + } + + /** + * RectCash provides simple creating MultiRectArea + */ + public static class RectCash extends MultiRectArea { + + int[] cash; + + public RectCash() { + super(); + cash = new int[MultiRectAreaOp.RECT_CAPACITY]; + cash[0] = 1; + } + + public void addRectCashed(int x1, int y1, int x2, int y2) { + addRect(x1, y1, x2, y2); + invalidate(); +/* + // Exclude from cash unnecessary rectangles + int i = 1; + while(i < cash[0]) { + if (rect[cash[i] + 3] >= y1 - 1) { + if (i > 1) { + System.arraycopy(cash, i, cash, 1, cash[0] - i); + } + break; + } + i++; + } + cash[0] -= i - 1; + + // Find in cash rectangle to concatinate + i = 1; + while(i < cash[0]) { + int index = cash[i]; + if (rect[index + 3] != y1 - 1) { + break; + } + if (rect[index] == x1 && rect[index + 2] == x2) { + rect[index + 3] += y2 - y1 + 1; + + int pos = i + 1; + while(pos < cash[0]) { + if (rect[index + 3] <= rect[cash[i] + 3]) { + System.arraycopy(cash, i + 1, cash, i, pos - i); + break; + } + i++; + } + cash[pos - 1] = index; + + invalidate(); + return; + } + i++; + } + + // Add rectangle to buffer + int index = rect[0]; + rect = MultiRectAreaOp.checkBufSize(rect, 4); + rect[index + 0] = x1; + rect[index + 1] = y1; + rect[index + 2] = x2; + rect[index + 3] = y2; + + // Add rectangle to cash + int length = cash[0]; + cash = MultiRectAreaOp.checkBufSize(cash, 1); + while(i < length) { + if (y2 <= rect[cash[i] + 3]) { + System.arraycopy(cash, i, cash, i + 1, length - i); + break; + } + i++; + } + cash[i] = index; + invalidate(); +*/ + } + + public void addRectCashed(int[] rect, int rectOff, int rectLength) { + for(int i = rectOff; i < rectOff + rectLength;) { + addRect(rect[i++], rect[i++], rect[i++], rect[i++]); +// addRectCashed(rect[i++], rect[i++], rect[i++], rect[i++]); + } + } + + } + + /** + * MultiRectArea path iterator + */ + class Iterator implements PathIterator { + + int type; + int index; + int pos; + + int[] rect; + AffineTransform t; + + Iterator(MultiRectArea mra, AffineTransform t) { + rect = new int[mra.rect[0] - 1]; + System.arraycopy(mra.rect, 1, rect, 0, rect.length); + this.t = t; + } + + public int getWindingRule() { + return WIND_NON_ZERO; + } + + public boolean isDone() { + return pos >= rect.length; + } + + public void next() { + if (index == 4) { + pos += 4; + } + index = (index + 1) % 5; + } + + public int currentSegment(double[] coords) { + if (isDone()) { + // awt.4B=Iiterator out of bounds + throw new NoSuchElementException(Messages.getString("awt.4B")); //$NON-NLS-1$ + } + int type = 0; + + switch(index) { + case 0 : + type = SEG_MOVETO; + coords[0] = rect[pos + 0]; + coords[1] = rect[pos + 1]; + break; + case 1: + type = SEG_LINETO; + coords[0] = rect[pos + 2]; + coords[1] = rect[pos + 1]; + break; + case 2: + type = SEG_LINETO; + coords[0] = rect[pos + 2]; + coords[1] = rect[pos + 3]; + break; + case 3: + type = SEG_LINETO; + coords[0] = rect[pos + 0]; + coords[1] = rect[pos + 3]; + break; + case 4: + type = SEG_CLOSE; + break; + } + + if (t != null) { + t.transform(coords, 0, coords, 0, 1); + } + return type; + } + + public int currentSegment(float[] coords) { + if (isDone()) { + // awt.4B=Iiterator out of bounds + throw new NoSuchElementException(Messages.getString("awt.4B")); //$NON-NLS-1$ + } + int type = 0; + + switch(index) { + case 0 : + type = SEG_MOVETO; + coords[0] = rect[pos + 0]; + coords[1] = rect[pos + 1]; + break; + case 1: + type = SEG_LINETO; + coords[0] = rect[pos + 2]; + coords[1] = rect[pos + 1]; + break; + case 2: + type = SEG_LINETO; + coords[0] = rect[pos + 2]; + coords[1] = rect[pos + 3]; + break; + case 3: + type = SEG_LINETO; + coords[0] = rect[pos + 0]; + coords[1] = rect[pos + 3]; + break; + case 4: + type = SEG_CLOSE; + break; + } + + if (t != null) { + t.transform(coords, 0, coords, 0, 1); + } + return type; + } + + } + + /** + * Constructs a new empty MultiRectArea + */ + public MultiRectArea() { + rect = MultiRectAreaOp.createBuf(0); + } + + public MultiRectArea(boolean sorted) { + this(); + this.sorted = sorted; + } + + /** + * Constructs a new MultiRectArea as a copy of another one + */ + public MultiRectArea(MultiRectArea mra) { + if (mra == null) { + rect = MultiRectAreaOp.createBuf(0); + } else { + rect = new int[mra.rect.length]; + System.arraycopy(mra.rect, 0, rect, 0, mra.rect.length); + check(this, "MultiRectArea(MRA)"); //$NON-NLS-1$ + } + } + + /** + * Constructs a new MultiRectArea consists of single rectangle + */ + public MultiRectArea(Rectangle r) { + rect = MultiRectAreaOp.createBuf(0); + if (r != null && !r.isEmpty()) { + rect[0] = 5; + rect[1] = r.x; + rect[2] = r.y; + rect[3] = r.x + r.width - 1; + rect[4] = r.y + r.height - 1; + } + check(this, "MultiRectArea(Rectangle)"); //$NON-NLS-1$ + } + + /** + * Constructs a new MultiRectArea consists of single rectangle + */ + public MultiRectArea(int x0, int y0, int x1, int y1) { + rect = MultiRectAreaOp.createBuf(0); + if (x1 >= x0 && y1 >= y0) { + rect[0] = 5; + rect[1] = x0; + rect[2] = y0; + rect[3] = x1; + rect[4] = y1; + } + check(this, "MultiRectArea(Rectangle)"); //$NON-NLS-1$ + } + + /** + * Constructs a new MultiRectArea and append rectangle from buffer + */ + public MultiRectArea(Rectangle[] buf) { + this(); + for (Rectangle element : buf) { + add(element); + } + } + + /** + * Constructs a new MultiRectArea and append rectangle from array + */ + public MultiRectArea(ArrayList buf) { + this(); + for(int i = 0; i < buf.size(); i++) { + add(buf.get(i)); + } + } + + /** + * Sort rectangle buffer + */ + void resort() { + int[] buf = new int[4]; + for(int i = 1; i < rect[0]; i += 4) { + int k = i; + int x1 = rect[k]; + int y1 = rect[k + 1]; + for(int j = i + 4; j < rect[0]; j += 4) { + int x2 = rect[j]; + int y2 = rect[j + 1]; + if (y1 > y2 || (y1 == y2 && x1 > x2)) { + x1 = x2; + y1 = y2; + k = j; + } + } + if (k != i) { + System.arraycopy(rect, i, buf, 0, 4); + System.arraycopy(rect, k, rect, i, 4); + System.arraycopy(buf, 0, rect, k, 4); + } + } + invalidate(); + } + + /** + * Tests equals with another object + */ + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof MultiRectArea) { + MultiRectArea mra = (MultiRectArea) obj; + for(int i = 0; i < rect[0]; i++) { + if (rect[i] != mra.rect[i]) { + return false; + } + } + return true; + } + return false; + } + + /** + * Checks validation of MultiRectArea object + */ + static MultiRectArea check(MultiRectArea mra, String msg) { + if (CHECK && mra != null) { + if (MultiRectArea.checkValidation(mra.getRectangles(), mra.sorted) != -1) { + // awt.4C=Invalid MultiRectArea in method {0} + new RuntimeException(Messages.getString("awt.4C", msg)); //$NON-NLS-1$ + } + } + return mra; + } + + /** + * Checks validation of MultiRectArea object + */ + public static int checkValidation(Rectangle[] r, boolean sorted) { + + // Check width and height + for(int i = 0; i < r.length; i++) { + if (r[i].width <= 0 || r[i].height <= 0) { + return i; + } + } + + // Check order + if (sorted) { + for(int i = 1; i < r.length; i++) { + if (r[i - 1].y > r[i].y) { + return i; + } + if (r[i - 1].y == r[i].y) { + if (r[i - 1].x > r[i].x) { + return i; + } + } + } + } + + // Check override + for(int i = 0; i < r.length; i++) { + for(int j = i + 1; j < r.length; j++) { + if (r[i].intersects(r[j])) { + return i; + } + } + } + + return -1; + } + + /** + * Assigns rectangle from another buffer + */ + protected void setRect(int[] buf, boolean copy) { + if (copy) { + rect = new int[buf.length]; + System.arraycopy(buf, 0, rect, 0, buf.length); + } else { + rect = buf; + } + invalidate(); + } + + /** + * Union with another MultiRectArea object + */ + public void add(MultiRectArea mra) { + setRect(union(this, mra).rect, false); + invalidate(); + } + + /** + * Intersect with another MultiRectArea object + */ + public void intersect(MultiRectArea mra) { + setRect(intersect(this, mra).rect, false); + invalidate(); + } + + /** + * Subtract another MultiRectArea object + */ + public void substract(MultiRectArea mra) { + setRect(subtract(this, mra).rect, false); + invalidate(); + } + + /** + * Union with Rectangle object + */ + public void add(Rectangle rect) { + setRect(union(this, new MultiRectArea(rect)).rect, false); + invalidate(); + } + + /** + * Intersect with Rectangle object + */ + public void intersect(Rectangle rect) { + setRect(intersect(this, new MultiRectArea(rect)).rect, false); + invalidate(); + } + + /** + * Subtract rectangle object + */ + public void substract(Rectangle rect) { + setRect(subtract(this, new MultiRectArea(rect)).rect, false); + } + + /** + * Union two MutliRectareArea objects + */ + public static MultiRectArea intersect(MultiRectArea src1, MultiRectArea src2) { + MultiRectArea res = check(MultiRectAreaOp.Intersection.getResult(src1, src2), "intersect(MRA,MRA)"); //$NON-NLS-1$ + return res; + } + + /** + * Intersect two MultiRectArea objects + */ + public static MultiRectArea union(MultiRectArea src1, MultiRectArea src2) { + MultiRectArea res = check(new MultiRectAreaOp.Union().getResult(src1, src2), "union(MRA,MRA)"); //$NON-NLS-1$ + return res; + } + + /** + * Subtract two MultiRectArea objects + */ + public static MultiRectArea subtract(MultiRectArea src1, MultiRectArea src2) { + MultiRectArea res = check(MultiRectAreaOp.Subtraction.getResult(src1, src2), "subtract(MRA,MRA)"); //$NON-NLS-1$ + return res; + } + + /** + * Print MultiRectArea object to output stream + */ + public static void print(MultiRectArea mra, String msg) { + if (mra == null) { + System.out.println(msg + "=null"); //$NON-NLS-1$ + } else { + Rectangle[] rects = mra.getRectangles(); + System.out.println(msg + "(" + rects.length + ")"); //$NON-NLS-1$ //$NON-NLS-2$ + for (Rectangle element : rects) { + System.out.println( + element.x + "," + //$NON-NLS-1$ + element.y + "," + //$NON-NLS-1$ + (element.x + element.width - 1) + "," + //$NON-NLS-1$ + (element.y + element.height - 1)); + } + } + } + + /** + * Translate MultiRectArea object by (x, y) + */ + public void translate(int x, int y) { + for(int i = 1; i < rect[0];) { + rect[i++] += x; + rect[i++] += y; + rect[i++] += x; + rect[i++] += y; + } + + if (bounds != null && !bounds.isEmpty()) { + bounds.translate(x, y); + } + + if (rectangles != null) { + for (Rectangle element : rectangles) { + element.translate(x, y); + } + } + } + + /** + * Add rectangle to the buffer without any checking + */ + public void addRect(int x1, int y1, int x2, int y2) { + int i = rect[0]; + rect = MultiRectAreaOp.checkBufSize(rect, 4); + rect[i++] = x1; + rect[i++] = y1; + rect[i++] = x2; + rect[i++] = y2; + } + + /** + * Tests is MultiRectArea empty + */ + public boolean isEmpty() { + return rect[0] == 1; + } + + void invalidate() { + bounds = null; + rectangles = null; + } + + /** + * Returns bounds of MultiRectArea object + */ + public Rectangle getBounds() { + if (bounds != null) { + return bounds; + } + + if (isEmpty()) { + return bounds = new Rectangle(); + } + + int x1 = rect[1]; + int y1 = rect[2]; + int x2 = rect[3]; + int y2 = rect[4]; + + for(int i = 5; i < rect[0]; i += 4) { + int rx1 = rect[i + 0]; + int ry1 = rect[i + 1]; + int rx2 = rect[i + 2]; + int ry2 = rect[i + 3]; + if (rx1 < x1) { + x1 = rx1; + } + if (rx2 > x2) { + x2 = rx2; + } + if (ry1 < y1) { + y1 = ry1; + } + if (ry2 > y2) { + y2 = ry2; + } + } + + return bounds = new Rectangle(x1, y1, x2 - x1 + 1, y2 - y1 + 1); + } + + /** + * Recturn rectangle count in the buffer + */ + public int getRectCount() { + return (rect[0] - 1) / 4; + } + + /** + * Returns Rectangle array + */ + public Rectangle[] getRectangles() { + if (rectangles != null) { + return rectangles; + } + + rectangles = new Rectangle[(rect[0] - 1) / 4]; + int j = 0; + for(int i = 1; i < rect[0]; i += 4) { + rectangles[j++] = new Rectangle( + rect[i], + rect[i + 1], + rect[i + 2] - rect[i] + 1, + rect[i + 3] - rect[i + 1] + 1); + } + return rectangles; + } + + /** + * Returns Bounds2D + */ + public Rectangle2D getBounds2D() { + return getBounds(); + } + + /** + * Tests does point lie inside MultiRectArea object + */ + public boolean contains(double x, double y) { + for(int i = 1; i < rect[0]; i+= 4) { + if (rect[i] <= x && x <= rect[i + 2] && rect[i + 1] <= y && y <= rect[i + 3]) { + return true; + } + } + return false; + } + + /** + * Tests does Point2D lie inside MultiRectArea object + */ + public boolean contains(Point2D p) { + return contains(p.getX(), p.getY()); + } + + /** + * Tests does rectangle lie inside MultiRectArea object + */ + public boolean contains(double x, double y, double w, double h) { + throw new RuntimeException("Not implemented"); //$NON-NLS-1$ + } + + /** + * Tests does Rectangle2D lie inside MultiRectArea object + */ + public boolean contains(Rectangle2D r) { + throw new RuntimeException("Not implemented"); //$NON-NLS-1$ + } + + /** + * Tests does rectangle intersect MultiRectArea object + */ + public boolean intersects(double x, double y, double w, double h) { + Rectangle r = new Rectangle(); + r.setRect(x, y, w, h); + return intersects(r); + } + + /** + * Tests does Rectangle2D intersect MultiRectArea object + */ + public boolean intersects(Rectangle2D r) { + if (r == null || r.isEmpty()) { + return false; + } + for(int i = 1; i < rect[0]; i+= 4) { + if (r.intersects(rect[i], rect[i+1], rect[i + 2]-rect[i]+1, rect[i + 3]-rect[i + 1]+1)) { + return true; + } + } + return false; + } + + /** + * Returns path iterator + */ + public PathIterator getPathIterator(AffineTransform t, double flatness) { + return new Iterator(this, t); + } + + /** + * Returns path iterator + */ + public PathIterator getPathIterator(AffineTransform t) { + return new Iterator(this, t); + } + + /** + * Returns MultiRectArea object converted to string + */ + @Override + public String toString() { + int cnt = getRectCount(); + StringBuffer sb = new StringBuffer((cnt << 5) + 128); + sb.append(getClass().getName()).append(" ["); //$NON-NLS-1$ + for(int i = 1; i < rect[0]; i += 4) { + sb.append(i > 1 ? ", [" : "[").append(rect[i]).append(", ").append(rect[i + 1]). //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + append(", ").append(rect[i + 2] - rect[i] + 1).append(", "). //$NON-NLS-1$ //$NON-NLS-2$ + append(rect[i + 3] - rect[i + 1] + 1).append("]"); //$NON-NLS-1$ + } + return sb.append("]").toString(); //$NON-NLS-1$ + } + +} + diff --git a/awt/org/apache/harmony/awt/gl/MultiRectAreaOp.java b/awt/org/apache/harmony/awt/gl/MultiRectAreaOp.java new file mode 100644 index 0000000..c75e203 --- /dev/null +++ b/awt/org/apache/harmony/awt/gl/MultiRectAreaOp.java @@ -0,0 +1,837 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Denis M. Kishenko + * @version $Revision$ + */ +package org.apache.harmony.awt.gl; + +import java.awt.Rectangle; + +public class MultiRectAreaOp { + + /** + * Rectangle buffer capacity + */ + public static final int RECT_CAPACITY = 16; + + /** + * If number of rectangle in MultiRectArea object less than MAX_SIMPLE simple algorithm applies + */ + private static final int MAX_SIMPLE = 8; + + /** + * Create buffer + */ + public static int[] createBuf(int capacity) { + if (capacity == 0) { + capacity = RECT_CAPACITY; + } + int[] buf = new int[capacity]; + buf[0] = 1; + return buf; + } + + /** + * Checks buffer size and reallocate if necessary + */ + public static int[] checkBufSize(int[] buf, int capacity) { + if (buf[0] + capacity >= buf.length) { + int length = buf[0] + (capacity > RECT_CAPACITY ? capacity : RECT_CAPACITY); + int[] tmp = new int[length]; + System.arraycopy(buf, 0, tmp, 0, buf[0]); + buf = tmp; + } + buf[0] += capacity; + return buf; + } + + /** + * Region class provides basic functionlity for MultiRectArea objects to make logical operations + */ + static class Region { + + int[] region; + int[] active; + int[] bottom; + int index; + + public Region(int[] region) { + this.region = region; + active = new int[RECT_CAPACITY]; + bottom = new int[RECT_CAPACITY]; + active[0] = 1; + bottom[0] = 1; + index = 1; + } + + void addActive(int index) { + int length = active[0]; + active = checkBufSize(active, 4); + int i = 1; + + while(i < length) { + if (region[index] < active[i]) { + // Insert + System.arraycopy(active, i, active, i + 4, length - i); + length = i; + break; + } + i += 4; + } + System.arraycopy(region, index, active, length, 4); + + } + + void findActive(int top, int bottom) { + while(index < region[0]) { + if (region[index + 1] > bottom) { // y1 > bottom + return; + } + if (region[index + 3] >= top) { // y2 >= top + addActive(index); + } + index += 4; + } + } + + void deleteActive(int bottom) { + int length = active[0]; + for(int i = 1; i < length;) { + if (active[i + 3] == bottom) { + length -= 4; + if (i < length) { + System.arraycopy(active, i + 4, active, i, length - i); + } + } else { + i += 4; + } + } + active[0] = length; + } + + void deleteActive() { + int length = active[0]; + for(int i = length - 4; i > 0; i -= 4) { + if (active[i + 1] > active[i + 3]) { + length -= 4; + if (i < length) { + System.arraycopy(active, i + 4, active, i, length - i); + } + } + } + active[0] = length; + } + + void createLevel(int[] level) { + int levelCount = 1; + int topIndex = 1; + int i = 1; + while(i < region[0]) { + + int top = region[i + 1]; + int bottom = region[i + 3] + 1; + int j = topIndex; + + addTop: { + while(j < levelCount) { + if (level[j] == top) { + break addTop; + } + if (level[j] > top) { + System.arraycopy(level, j, level, j + 1, levelCount - j); + break; + } + j++; + } + + level[j] = top; + levelCount++; + topIndex = j; + } + + addBottom: { + while(j < levelCount) { + if (level[j] == bottom) { + break addBottom; + } + if (level[j] > bottom) { + System.arraycopy(level, j, level, j + 1, levelCount - j); + break; + } + j++; + }; + + level[j] = bottom; + levelCount++; + } + + i += 4; + } + level[0] = levelCount; + } + + static void sortOrdered(int[] src1, int[] src2, int[] dst) { + int length1 = src1[0]; + int length2 = src2[0]; + int count = 1; + int i1 = 1; + int i2 = 1; + int v1 = src1[1]; + int v2 = src2[1]; + while(true) { + + LEFT: { + while(i1 < length1) { + v1 = src1[i1]; + if (v1 >= v2) { + break LEFT; + } + dst[count++] = v1; + i1++; + } + while(i2 < length2) { + dst[count++] = src2[i2++]; + } + dst[0] = count; + return; + } + + RIGHT: { + while(i2 < length2) { + v2 = src2[i2]; + if (v2 >= v1) { + break RIGHT; + } + dst[count++] = v2; + i2++; + } + while(i1 < length1) { + dst[count++] = src1[i1++]; + } + dst[0] = count; + return; + } + + if (v1 == v2) { + dst[count++] = v1; + i1++; + i2++; + if (i1 < length1) { + v1 = src1[i1]; + } + if (i2 < length2 - 1) { + v2 = src2[i2]; + } + } + } + // UNREACHABLE + } + + } + + /** + * Intersection class provides intersection of two MultiRectAre aobjects + */ + static class Intersection { + + static void intersectRegions(int[] reg1, int[] reg2, MultiRectArea.RectCash dst, int height1, int height2) { + + Region d1 = new Region(reg1); + Region d2 = new Region(reg2); + + int[] level = new int[height1 + height2]; + int[] level1 = new int[height1]; + int[] level2 = new int[height2]; + d1.createLevel(level1); + d2.createLevel(level2); + Region.sortOrdered(level1, level2, level); + + int top; + int bottom = level[1] - 1; + for(int i = 2; i < level[0]; i++) { + + top = bottom + 1; + bottom = level[i] - 1; + + d1.findActive(top, bottom); + d2.findActive(top, bottom); + + int i1 = 1; + int i2 = 1; + + while(i1 < d1.active[0] && i2 < d2.active[0]) { + + int x11 = d1.active[i1]; + int x12 = d1.active[i1 + 2]; + int x21 = d2.active[i2]; + int x22 = d2.active[i2 + 2]; + + if (x11 <= x21) { + if (x12 >= x21) { + if (x12 <= x22) { + dst.addRectCashed(x21, top, x12, bottom); + i1 += 4; + } else { + dst.addRectCashed(x21, top, x22, bottom); + i2 += 4; + } + } else { + i1 += 4; + } + } else { + if (x22 >= x11) { + if (x22 <= x12) { + dst.addRectCashed(x11, top, x22, bottom); + i2 += 4; + } else { + dst.addRectCashed(x11, top, x12, bottom); + i1 += 4; + } + } else { + i2 += 4; + } + } + } + + d1.deleteActive(bottom); + d2.deleteActive(bottom); + } + } + + static int[] simpleIntersect(MultiRectArea src1, MultiRectArea src2) { + int[] rect1 = src1.rect; + int[] rect2 = src2.rect; + int[] rect = createBuf(0); + + int k = 1; + for(int i = 1; i < rect1[0];) { + + int x11 = rect1[i++]; + int y11 = rect1[i++]; + int x12 = rect1[i++]; + int y12 = rect1[i++]; + + for(int j = 1; j < rect2[0];) { + + int x21 = rect2[j++]; + int y21 = rect2[j++]; + int x22 = rect2[j++]; + int y22 = rect2[j++]; + + if (x11 <= x22 && x12 >= x21 && + y11 <= y22 && y12 >= y21) + { + rect = checkBufSize(rect, 4); + rect[k++] = x11 > x21 ? x11 : x21; + rect[k++] = y11 > y21 ? y11 : y21; + rect[k++] = x12 > x22 ? x22 : x12; + rect[k++] = y12 > y22 ? y22 : y12; + } + } + } + + rect[0] = k; + return rect; + } + + public static MultiRectArea getResult(MultiRectArea src1, MultiRectArea src2) { + + if (src1 == null || src2 == null || src1.isEmpty() || src2.isEmpty()) { + return new MultiRectArea(); + } + + MultiRectArea.RectCash dst = new MultiRectArea.RectCash(); + + if (!src1.sorted || !src2.sorted || + src1.getRectCount() <= MAX_SIMPLE || src2.getRectCount() <= MAX_SIMPLE) + { + dst.setRect(simpleIntersect(src1, src2), false); + } else { + Rectangle bounds1 = src1.getBounds(); + Rectangle bounds2 = src2.getBounds(); + Rectangle bounds3 = bounds1.intersection(bounds2); + if (bounds3.width > 0 && bounds3.height > 0) { + intersectRegions(src1.rect, src2.rect, dst, bounds1.height + 2, bounds2.height + 2); + } + } + + return dst; + } + + } + + /** + * Union class provides union of two MultiRectAre aobjects + */ + static class Union { + + int rx1, rx2; + int top, bottom; + MultiRectArea.RectCash dst; + + boolean next(Region d, int index) { + int x1 = d.active[index]; + int x2 = d.active[index + 2]; + boolean res = false; + + if (x2 < rx1 - 1) { + res = true; + dst.addRectCashed(x1, top, x2, bottom); + } else + if (x1 > rx2 + 1) { + res = false; + dst.addRectCashed(rx1, top, rx2, bottom); + rx1 = x1; + rx2 = x2; + } else { + res = x2 <= rx2; + rx1 = Math.min(x1, rx1); + rx2 = Math.max(x2, rx2); + } + + // Top + if (d.active[index + 1] < top) { + dst.addRectCashed(x1, d.active[index + 1], x2, top - 1); + } + // Bottom + if (d.active[index + 3] > bottom) { + d.active[index + 1] = bottom + 1; + } + return res; + } + + void check(Region d, int index, boolean t) { + int x1 = d.active[index]; + int x2 = d.active[index + 2]; + // Top + if (d.active[index + 1] < top) { + dst.addRectCashed(x1, d.active[index + 1], x2, top - 1); + } + if (t) { + dst.addRectCashed(x1, top, x2, bottom); + } + // Bottom + if (d.active[index + 3] > bottom) { + d.active[index + 1] = bottom + 1; + } + } + + void unionRegions(int[] reg1, int[] reg2, int height1, int height2) { + Region d1 = new Region(reg1); + Region d2 = new Region(reg2); + + int[] level = new int[height1 + height2]; + int[] level1 = new int[height1]; + int[] level2 = new int[height2]; + d1.createLevel(level1); + d2.createLevel(level2); + Region.sortOrdered(level1, level2, level); + + bottom = level[1] - 1; + for(int i = 2; i < level[0]; i++) { + + top = bottom + 1; + bottom = level[i] - 1; + + d1.findActive(top, bottom); + d2.findActive(top, bottom); + + int i1 = 1; + int i2 = 1; + boolean res1, res2; + + if (d1.active[0] > 1) { + check(d1, 1, false); + rx1 = d1.active[1]; + rx2 = d1.active[3]; + i1 += 4; + res1 = false; + res2 = true; + } else + if (d2.active[0] > 1) { + check(d2, 1, false); + rx1 = d2.active[1]; + rx2 = d2.active[3]; + i2 += 4; + res1 = true; + res2 = false; + } else { + continue; + } + + outer: + while(true) { + + while (res1) { + if (i1 >= d1.active[0]) { + dst.addRectCashed(rx1, top, rx2, bottom); + while(i2 < d2.active[0]) { + check(d2, i2, true); + i2 += 4; + } + break outer; + } + res1 = next(d1, i1); + i1 += 4; + } + + while (res2) { + if (i2 >= d2.active[0]) { + dst.addRectCashed(rx1, top, rx2, bottom); + while(i1 < d1.active[0]) { + check(d1, i1, true); + i1 += 4; + } + break outer; + } + res2 = next(d2, i2); + i2 += 4; + } + + res1 = true; + res2 = true; + } // while + + d1.deleteActive(bottom); + d2.deleteActive(bottom); + + } + } + + static void simpleUnion(MultiRectArea src1, MultiRectArea src2, MultiRectArea dst) { + if (src1.getRectCount() < src2.getRectCount()) { + simpleUnion(src2, src1, dst); + } else { + Subtraction.simpleSubtract(src1, src2, dst); + int pos = dst.rect[0]; + int size = src2.rect[0] - 1; + dst.rect = checkBufSize(dst.rect, size); + System.arraycopy(src2.rect,1, dst.rect, pos, size); + dst.resort(); + } + } + + MultiRectArea getResult(MultiRectArea src1, MultiRectArea src2) { + + if (src1 == null || src1.isEmpty()) { + return new MultiRectArea(src2); + } + + if (src2 == null || src2.isEmpty()) { + return new MultiRectArea(src1); + } + + dst = new MultiRectArea.RectCash(); + + if (!src1.sorted || !src2.sorted || + src1.getRectCount() <= MAX_SIMPLE || src2.getRectCount() <= MAX_SIMPLE) + { + simpleUnion(src1, src2, dst); + } else { + Rectangle bounds1 = src1.getBounds(); + Rectangle bounds2 = src2.getBounds(); + Rectangle bounds3 = bounds1.intersection(bounds2); + + if (bounds3.width < 0 || bounds3.height < 0) { + if (bounds1.y + bounds1.height < bounds2.y) { + dst.setRect(addVerRegion(src1.rect, src2.rect), false); + } else + if (bounds2.y + bounds2.height < bounds1.y) { + dst.setRect(addVerRegion(src2.rect, src1.rect), false); + } else + if (bounds1.x < bounds2.x) { + dst.setRect(addHorRegion(src1.rect, src2.rect), false); + } else { + dst.setRect(addHorRegion(src2.rect, src1.rect), false); + } + } else { + unionRegions(src1.rect, src2.rect, bounds1.height + 2, bounds2.height + 2); + } + } + + return dst; + } + + int[] addVerRegion(int[] top, int[] bottom) { + int length = top[0] + bottom[0] - 1; + int[] dst = new int[length]; + dst[0] = length; + System.arraycopy(top, 1, dst, 1, top[0] - 1); + System.arraycopy(bottom, 1, dst, top[0], bottom[0] - 1); + return dst; + } + + int[] addHorRegion(int[] left, int[] right) { + int count1 = left[0]; + int count2 = right[0]; + int[] dst = new int[count1 + count2 + 1]; + int count = 1; + int index1 = 1; + int index2 = 1; + + int top1 = left[2]; + int top2 = right[2]; + int pos1, pos2; + + while(true) { + + if (index1 >= count1) { + System.arraycopy(right, index2, dst, count, count2 - index2); + count += count2 - index2; + break; + } + if (index2 >= count2) { + System.arraycopy(left, index1, dst, count, count1 - index1); + count += count1 - index1; + break; + } + + if (top1 < top2) { + pos1 = index1; + do { + index1 += 4; + } while (index1 < count1 && (top1 = left[index1 + 1]) < top2); + System.arraycopy(left, pos1, dst, count, index1 - pos1); + count += index1 - pos1; + continue; + } + + if (top1 > top2) { + pos2 = index2; + do { + index2 += 4; + } while (index2 < count2 && (top2 = right[index2 + 1]) < top1); + System.arraycopy(right, pos2, dst, count, index2 - pos2); + count += index2 - pos2; + continue; + } + + int top = top1; + pos1 = index1; + pos2 = index2; + do { + index1 += 4; + } while(index1 < count1 && (top1 = left[index1 + 1]) == top); + do { + index2 += 4; + } while(index2 < count2 && (top2 = right[index2 + 1]) == top); + + System.arraycopy(left, pos1, dst, count, index1 - pos1); + count += index1 - pos1; + System.arraycopy(right, pos2, dst, count, index2 - pos2); + count += index2 - pos2; + } + + dst[0] = count; + return dst; + } + + } + + /** + * Subtraction class provides subtraction of two MultiRectAre aobjects + */ + static class Subtraction { + + static void subtractRegions(int[] reg1, int[] reg2, MultiRectArea.RectCash dst, int height1, int height2) { + Region d1 = new Region(reg1); + Region d2 = new Region(reg2); + + int[] level = new int[height1 + height2]; + int[] level1 = new int[height1]; + int[] level2 = new int[height2]; + d1.createLevel(level1); + d2.createLevel(level2); + Region.sortOrdered(level1, level2, level); + + int top; + int bottom = level[1] - 1; + for(int i = 2; i < level[0]; i++) { + + top = bottom + 1; + bottom = level[i] - 1; + + d1.findActive(top, bottom); + if (d1.active[0] == 1) { + d2.deleteActive(bottom); + continue; + } + + d2.findActive(top, bottom); + + int i1 = 1; + int i2 = 1; + + int rx1 = 0; + int rx2 = 0; + + boolean next = true; + + while(true) { + + if (next) { + next = false; + if (i1 >= d1.active[0]) { + break; + } + // Bottom + d1.active[i1 + 1] = bottom + 1; + rx1 = d1.active[i1]; + rx2 = d1.active[i1 + 2]; + i1 += 4; + } + + if (i2 >= d2.active[0]) { + dst.addRectCashed(rx1, top, rx2, bottom); + for(int j = i1; j < d1.active[0]; j += 4) { + dst.addRectCashed(d1.active[j], top, d1.active[j + 2], bottom); + d1.active[j + 1] = bottom + 1; + } + break; + } + + int x1 = d2.active[i2]; + int x2 = d2.active[i2 + 2]; + + if (rx1 < x1) { + if (rx2 >= x1) { + if (rx2 <= x2) { + // [-----------] + // [-------------] + dst.addRectCashed(rx1, top, x1 - 1, bottom); + next = true; + } else { + // [-----------------] + // [------] + dst.addRectCashed(rx1, top, x1 - 1, bottom); + rx1 = x2 + 1; + i2 += 4; + } + } else { + // [-----] + // [----] + dst.addRectCashed(rx1, top, rx2, bottom); + next = true; + } + } else { + if (rx1 <= x2) { + if (rx2 <= x2) { + // [------] + // [-----------] + next = true; + } else { + // [------------] + // [---------] + rx1 = x2 + 1; + i2 += 4; + } + } else { + // [----] + // [-----] + i2 += 4; + } + } + + } + d1.deleteActive(); + d2.deleteActive(bottom); + } + } + + static void subtractRect(int x11, int y11, int x12, int y12, int[] rect, int index, MultiRectArea dst) { + + for(int i = index; i < rect[0]; i += 4) { + int x21 = rect[i + 0]; + int y21 = rect[i + 1]; + int x22 = rect[i + 2]; + int y22 = rect[i + 3]; + + if (x11 <= x22 && x12 >= x21 && y11 <= y22 && y12 >= y21) { + int top, bottom; + if (y11 < y21) { + subtractRect(x11, y11, x12, y21 - 1, rect, i + 4, dst); + top = y21; + } else { + top = y11; + } + if (y12 > y22) { + subtractRect(x11, y22 + 1, x12, y12, rect, i + 4, dst); + bottom = y22; + } else { + bottom = y12; + } + if (x11 < x21) { + subtractRect(x11, top, x21 - 1, bottom, rect, i + 4, dst); + } + if (x12 > x22) { + subtractRect(x22 + 1, top, x12, bottom, rect, i + 4, dst); + } + return; + } + } + dst.addRect(x11, y11, x12, y12); + } + + static void simpleSubtract(MultiRectArea src1, MultiRectArea src2, MultiRectArea dst) { + for(int i = 1; i < src1.rect[0]; i += 4) { + subtractRect( + src1.rect[i + 0], + src1.rect[i + 1], + src1.rect[i + 2], + src1.rect[i + 3], + src2.rect, + 1, + dst); + } + dst.resort(); + } + + public static MultiRectArea getResult(MultiRectArea src1, MultiRectArea src2) { + + if (src1 == null || src1.isEmpty()) { + return new MultiRectArea(); + } + + if (src2 == null || src2.isEmpty()) { + return new MultiRectArea(src1); + } + + MultiRectArea.RectCash dst = new MultiRectArea.RectCash(); + + if (!src1.sorted || !src2.sorted || + src1.getRectCount() <= MAX_SIMPLE || src2.getRectCount() <= MAX_SIMPLE) + { + simpleSubtract(src1, src2, dst); + } else { + Rectangle bounds1 = src1.getBounds(); + Rectangle bounds2 = src2.getBounds(); + Rectangle bounds3 = bounds1.intersection(bounds2); + + if (bounds3.width > 0 && bounds3.height > 0) { + subtractRegions(src1.rect, src2.rect, dst, bounds1.height + 2, bounds2.height + 2); + } else { + dst.setRect(src1.rect, true); + } + } + + return dst; + } + + } + +} diff --git a/awt/org/apache/harmony/awt/gl/Surface.java b/awt/org/apache/harmony/awt/gl/Surface.java new file mode 100644 index 0000000..8b0ae38 --- /dev/null +++ b/awt/org/apache/harmony/awt/gl/Surface.java @@ -0,0 +1,309 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Igor V. Stolyarov + * @version $Revision$ + * Created on 10.11.2005 + * + */ +package org.apache.harmony.awt.gl; + +import java.awt.Image; +import java.awt.Transparency; +import java.awt.color.ColorSpace; +import java.awt.image.BufferedImage; +import java.awt.image.ColorModel; +import java.awt.image.ComponentColorModel; +import java.awt.image.ComponentSampleModel; +import java.awt.image.DataBuffer; +import java.awt.image.DirectColorModel; +import java.awt.image.IndexColorModel; +import java.awt.image.MultiPixelPackedSampleModel; +import java.awt.image.SampleModel; +import java.awt.image.WritableRaster; +import java.util.ArrayList; + +import org.apache.harmony.awt.gl.color.LUTColorConverter; + + +/** + * This class is super class for others types of Surfaces. + * Surface is storing data and data format description, that are using + * in blitting operations + */ +public abstract class Surface implements Transparency{ + + // Color Space Types + public static final int sRGB_CS = 1; + public static final int Linear_RGB_CS = 2; + public static final int Linear_Gray_CS = 3; + public static final int Custom_CS = 0; + + // Color Model Types + public static final int DCM = 1; // Direct Color Model + public static final int ICM = 2; // Index Color Model + public static final int CCM = 3; // Component Color Model + + // Sample Model Types + public static final int SPPSM = 1; // Single Pixel Packed Sample Model + public static final int MPPSM = 2; // Multi Pixel Packed Sample Model + public static final int CSM = 3; // Component Sample Model + public static final int PISM = 4; // Pixel Interleaved Sample Model + public static final int BSM = 5; // Banded Sample Model + + // Surface Types + private static final int ALPHA_MASK = 0xff000000; + private static final int RED_MASK = 0x00ff0000; + private static final int GREEN_MASK = 0x0000ff00; + private static final int BLUE_MASK = 0x000000ff; + private static final int RED_BGR_MASK = 0x000000ff; + private static final int GREEN_BGR_MASK = 0x0000ff00; + private static final int BLUE_BGR_MASK = 0x00ff0000; + private static final int RED_565_MASK = 0xf800; + private static final int GREEN_565_MASK = 0x07e0; + private static final int BLUE_565_MASK = 0x001f; + private static final int RED_555_MASK = 0x7c00; + private static final int GREEN_555_MASK = 0x03e0; + private static final int BLUE_555_MASK = 0x001f; + + static{ + //???AWT + /* + System.loadLibrary("gl"); //$NON-NLS-1$ + initIDs(); + */ + } + + + protected long surfaceDataPtr; // Pointer for Native Surface data + protected int transparency = OPAQUE; + protected int width; + protected int height; + + /** + * This list contains caches with the data of this surface that are valid at the moment. + * Surface should clear this list when its data is updated. + * Caches may check if they are still valid using isCacheValid method. + * When cache gets data from the surface, it should call addValidCache method of the surface. + */ + private final ArrayList validCaches = new ArrayList(); + + public abstract ColorModel getColorModel(); + public abstract WritableRaster getRaster(); + public abstract int getSurfaceType(); // Syrface type. It is equal + // BufferedImge type + /** + * Lock Native Surface data + */ + public abstract long lock(); + + /** + * Unlock Native Surface data + */ + public abstract void unlock(); + + /** + * Dispose Native Surface data + */ + public abstract void dispose(); + public abstract Surface getImageSurface(); + + public long getSurfaceDataPtr(){ + return surfaceDataPtr; + } + + public final boolean isCaheValid(Object cache) { + return validCaches.contains(cache); + } + + public final void addValidCache(Object cache) { + validCaches.add(cache); + } + + protected final void clearValidCaches() { + validCaches.clear(); + } + + /** + * Returns could or coldn't the Surface be blit by Native blitter + * @return - true if the Surface could be blit by Native blitter, + * false in other case + */ + public boolean isNativeDrawable(){ + return true; + } + + public int getTransparency() { + return transparency; + } + + public int getWidth(){ + return width; + } + + public int getHeight(){ + return height; + } + + /** + * If Surface has Raster, this method returns data array of Raster's DataBuffer + * @return - data array + */ + public Object getData(){ + return null; + } + + public boolean invalidated(){ + return true; + } + + public void validate(){} + + public void invalidate(){} + + /** + * Computation type of BufferedImage or Surface + * @param cm - ColorModel + * @param raster - WritableRaste + * @return - type of BufferedImage + */ + public static int getType(ColorModel cm, WritableRaster raster){ + int transferType = cm.getTransferType(); + boolean hasAlpha = cm.hasAlpha(); + ColorSpace cs = cm.getColorSpace(); + int csType = cs.getType(); + SampleModel sm = raster.getSampleModel(); + + if(csType == ColorSpace.TYPE_RGB){ + if(cm instanceof DirectColorModel){ + DirectColorModel dcm = (DirectColorModel) cm; + switch (transferType) { + case DataBuffer.TYPE_INT: + if (dcm.getRedMask() == RED_MASK && + dcm.getGreenMask() == GREEN_MASK && + dcm.getBlueMask() == BLUE_MASK) { + if (!hasAlpha) { + return BufferedImage.TYPE_INT_RGB; + } + if (dcm.getAlphaMask() == ALPHA_MASK) { + if (dcm.isAlphaPremultiplied()) { + return BufferedImage.TYPE_INT_ARGB_PRE; + } + return BufferedImage.TYPE_INT_ARGB; + } + return BufferedImage.TYPE_CUSTOM; + } else if (dcm.getRedMask() == RED_BGR_MASK && + dcm.getGreenMask() == GREEN_BGR_MASK && + dcm.getBlueMask() == BLUE_BGR_MASK) { + if (!hasAlpha) { + return BufferedImage.TYPE_INT_BGR; + } + } else { + return BufferedImage.TYPE_CUSTOM; + } + case DataBuffer.TYPE_USHORT: + if (dcm.getRedMask() == RED_555_MASK && + dcm.getGreenMask() == GREEN_555_MASK && + dcm.getBlueMask() == BLUE_555_MASK && !hasAlpha) { + return BufferedImage.TYPE_USHORT_555_RGB; + } else if (dcm.getRedMask() == RED_565_MASK && + dcm.getGreenMask() == GREEN_565_MASK && + dcm.getBlueMask() == BLUE_565_MASK) { + return BufferedImage.TYPE_USHORT_565_RGB; + } + default: + return BufferedImage.TYPE_CUSTOM; + } + }else if(cm instanceof IndexColorModel){ + IndexColorModel icm = (IndexColorModel) cm; + int pixelBits = icm.getPixelSize(); + if(transferType == DataBuffer.TYPE_BYTE){ + if(sm instanceof MultiPixelPackedSampleModel && !hasAlpha && + pixelBits < 5){ + return BufferedImage.TYPE_BYTE_BINARY; + }else if(pixelBits == 8){ + return BufferedImage.TYPE_BYTE_INDEXED; + } + } + return BufferedImage.TYPE_CUSTOM; + }else if(cm instanceof ComponentColorModel){ + ComponentColorModel ccm = (ComponentColorModel) cm; + if(transferType == DataBuffer.TYPE_BYTE && + sm instanceof ComponentSampleModel){ + ComponentSampleModel csm = + (ComponentSampleModel) sm; + int[] offsets = csm.getBandOffsets(); + int[] bits = ccm.getComponentSize(); + boolean isCustom = false; + for (int i = 0; i < bits.length; i++) { + if (bits[i] != 8 || + offsets[i] != offsets.length - 1 - i) { + isCustom = true; + break; + } + } + if (!isCustom) { + if (!ccm.hasAlpha()) { + return BufferedImage.TYPE_3BYTE_BGR; + } else if (ccm.isAlphaPremultiplied()) { + return BufferedImage.TYPE_4BYTE_ABGR_PRE; + } else { + return BufferedImage.TYPE_4BYTE_ABGR; + } + } + } + return BufferedImage.TYPE_CUSTOM; + } + return BufferedImage.TYPE_CUSTOM; + }else if(cs == LUTColorConverter.LINEAR_GRAY_CS){ + if(cm instanceof ComponentColorModel && + cm.getNumComponents() == 1){ + int bits[] = cm.getComponentSize(); + if(transferType == DataBuffer.TYPE_BYTE && + bits[0] == 8){ + return BufferedImage.TYPE_BYTE_GRAY; + }else if(transferType == DataBuffer.TYPE_USHORT && + bits[0] == 16){ + return BufferedImage.TYPE_USHORT_GRAY; + }else{ + return BufferedImage.TYPE_CUSTOM; + } + } + return BufferedImage.TYPE_CUSTOM; + } + return BufferedImage.TYPE_CUSTOM; + } + + public static Surface getImageSurface(Image image){ + return AwtImageBackdoorAccessor.getInstance().getImageSurface(image); + } + + @Override + protected void finalize() throws Throwable{ + dispose(); + } + + public static boolean isGrayPallete(IndexColorModel icm){ + return AwtImageBackdoorAccessor.getInstance().isGrayPallete(icm); + } + + /** + * Initialization of Native data + * + */ + //???AWT: private static native void initIDs(); +} diff --git a/awt/org/apache/harmony/awt/gl/TextRenderer.java b/awt/org/apache/harmony/awt/gl/TextRenderer.java new file mode 100644 index 0000000..f57952d --- /dev/null +++ b/awt/org/apache/harmony/awt/gl/TextRenderer.java @@ -0,0 +1,59 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Ilya S. Okomin + * @version $Revision$ + */ +package org.apache.harmony.awt.gl; + +import java.awt.Graphics2D; +import java.awt.font.GlyphVector; + +public abstract class TextRenderer { + + /** + * Draws string on specified Graphics at desired position. + * + * @param g specified Graphics2D object + * @param str String object to draw + * @param x start X position to draw + * @param y start Y position to draw + */ + public abstract void drawString(Graphics2D g, String str, float x, float y); + + /** + * Draws string on specified Graphics at desired position. + * + * @param g specified Graphics2D object + * @param str String object to draw + * @param x start X position to draw + * @param y start Y position to draw + */ + public void drawString(Graphics2D g, String str, int x, int y){ + drawString(g, str, (float)x, (float)y); + } + + /** + * Draws GlyphVector on specified Graphics at desired position. + * + * @param g specified Graphics2D object + * @param glyphVector GlyphVector object to draw + * @param x start X position to draw + * @param y start Y position to draw + */ + public abstract void drawGlyphVector(Graphics2D g, GlyphVector glyphVector, float x, float y); +} diff --git a/awt/org/apache/harmony/awt/gl/XORComposite.java b/awt/org/apache/harmony/awt/gl/XORComposite.java new file mode 100644 index 0000000..e27e1d3 --- /dev/null +++ b/awt/org/apache/harmony/awt/gl/XORComposite.java @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Igor V. Stolyarov + * @version $Revision$ + * Created on 21.11.2005 + * + */ +package org.apache.harmony.awt.gl; + +import java.awt.Color; +import java.awt.Composite; +import java.awt.CompositeContext; +import java.awt.RenderingHints; +import java.awt.image.ColorModel; + +public class XORComposite implements Composite { + + Color xorcolor; + + public XORComposite(Color xorcolor){ + this.xorcolor = xorcolor; + } + + public CompositeContext createContext(ColorModel srcCM, ColorModel dstCM, + RenderingHints hints) { + + return new ICompositeContext(this, srcCM, dstCM); + } + + public Color getXORColor(){ + return xorcolor; + } +} diff --git a/awt/org/apache/harmony/awt/gl/color/ColorConverter.java b/awt/org/apache/harmony/awt/gl/color/ColorConverter.java new file mode 100644 index 0000000..c98e114 --- /dev/null +++ b/awt/org/apache/harmony/awt/gl/color/ColorConverter.java @@ -0,0 +1,257 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Oleg V. Khaschansky + * @version $Revision$ + */ +package org.apache.harmony.awt.gl.color; + +import java.awt.color.ColorSpace; +import java.awt.image.BufferedImage; +import java.awt.image.ColorModel; +import java.awt.image.Raster; +import java.awt.image.WritableRaster; + +/** + * This class combines ColorScaler, ICC_Transform and NativeImageFormat functionality + * in the workflows for different types of input/output pixel data. + */ +public class ColorConverter { + private ColorScaler scaler = new ColorScaler(); + + public void loadScalingData(ColorSpace cs) { + scaler.loadScalingData(cs); + } + + /** + * Translates pixels, stored in source buffered image and writes the data + * to the destination image. + * @param t - ICC transform + * @param src - source image + * @param dst - destination image + */ + public void translateColor(ICC_Transform t, + BufferedImage src, BufferedImage dst) { + NativeImageFormat srcIF = NativeImageFormat.createNativeImageFormat(src); + NativeImageFormat dstIF = NativeImageFormat.createNativeImageFormat(dst); + + if (srcIF != null && dstIF != null) { + t.translateColors(srcIF, dstIF); + return; + } + + srcIF = createImageFormat(src); + dstIF = createImageFormat(dst); + + short srcChanData[] = (short[]) srcIF.getChannelData(); + short dstChanData[] = (short[]) dstIF.getChannelData(); + + ColorModel srcCM = src.getColorModel(); + int nColorChannels = srcCM.getNumColorComponents(); + scaler.loadScalingData(srcCM.getColorSpace()); // input scaling data + ColorModel dstCM = dst.getColorModel(); + + // Prepare array for alpha channel + float alpha[] = null; + boolean saveAlpha = srcCM.hasAlpha() && dstCM.hasAlpha(); + if (saveAlpha) { + alpha = new float[src.getWidth()*src.getHeight()]; + } + + WritableRaster wr = src.getRaster(); + int srcDataPos = 0, alphaPos = 0; + float normalizedVal[]; + for (int row=0, nRows = srcIF.getNumRows(); row profileHandles = new HashMap(); + + private static boolean isCMMLoaded; + + public static void addHandle(ICC_Profile key, long handle) { + profileHandles.put(key, new Long(handle)); + } + + public static void removeHandle(ICC_Profile key) { + profileHandles.remove(key); + } + + public static long getHandle(ICC_Profile key) { + return profileHandles.get(key).longValue(); + } + + /* ICC profile management */ + public static native long cmmOpenProfile(byte[] data); + public static native void cmmCloseProfile(long profileID); + public static native int cmmGetProfileSize(long profileID); + public static native void cmmGetProfile(long profileID, byte[] data); + public static native int cmmGetProfileElementSize(long profileID, int signature); + public static native void cmmGetProfileElement(long profileID, int signature, + byte[] data); + public static native void cmmSetProfileElement(long profileID, int tagSignature, + byte[] data); + + + /* ICC transforms */ + public static native long cmmCreateMultiprofileTransform( + long[] profileHandles, + int[] renderingIntents + ); + public static native void cmmDeleteTransform(long transformHandle); + public static native void cmmTranslateColors(long transformHandle, + NativeImageFormat src, + NativeImageFormat dest); + + static void loadCMM() { + if (!isCMMLoaded) { + AccessController.doPrivileged( + new PrivilegedAction() { + public Void run() { + System.loadLibrary("lcmm"); //$NON-NLS-1$ + return null; + } + } ); + isCMMLoaded = true; + } + } + + /* load native CMM library */ + static { + loadCMM(); + } +} diff --git a/awt/org/apache/harmony/awt/gl/color/NativeImageFormat.java b/awt/org/apache/harmony/awt/gl/color/NativeImageFormat.java new file mode 100644 index 0000000..9594047 --- /dev/null +++ b/awt/org/apache/harmony/awt/gl/color/NativeImageFormat.java @@ -0,0 +1,642 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Oleg V. Khaschansky + * @version $Revision$ + */ +package org.apache.harmony.awt.gl.color; + +import java.awt.image.BufferedImage; +import java.awt.image.ColorModel; +import java.awt.image.ComponentSampleModel; +import java.awt.image.DataBuffer; +import java.awt.image.Raster; +import java.awt.image.SampleModel; +import java.awt.image.SinglePixelPackedSampleModel; +import java.util.ArrayList; + +import org.apache.harmony.awt.gl.AwtImageBackdoorAccessor; +import org.apache.harmony.awt.internal.nls.Messages; + + +/** + * This class converts java color/sample models to the LCMS pixel formats. + * It also encapsulates all the information about the image format, which native CMM + * needs to have in order to read/write data. + * + * At present planar formats (multiple bands) are not supported + * and they are handled as a common (custom) case. + * Samples other than 1 - 7 bytes and multiple of 8 bits are + * also handled as custom (and won't be supported in the nearest future). + */ +class NativeImageFormat { + ////////////////////////////////////////////// + // LCMS Pixel types + private static final int PT_ANY = 0; // Don't check colorspace + // 1 & 2 are reserved + private static final int PT_GRAY = 3; + private static final int PT_RGB = 4; + // Skipping other since we don't use them here + /////////////////////////////////////////////// + + // Conversion of predefined BufferedImage formats to LCMS formats + private static final int INT_RGB_LCMS_FMT = + colorspaceSh(PT_RGB)| + extraSh(1)| + channelsSh(3)| + bytesSh(1)| + doswapSh(1)| + swapfirstSh(1); + + private static final int INT_ARGB_LCMS_FMT = INT_RGB_LCMS_FMT; + + private static final int INT_BGR_LCMS_FMT = + colorspaceSh(PT_RGB)| + extraSh(1)| + channelsSh(3)| + bytesSh(1); + + private static final int THREE_BYTE_BGR_LCMS_FMT = + colorspaceSh(PT_RGB)| + channelsSh(3)| + bytesSh(1)| + doswapSh(1); + + private static final int FOUR_BYTE_ABGR_LCMS_FMT = + colorspaceSh(PT_RGB)| + extraSh(1)| + channelsSh(3)| + bytesSh(1)| + doswapSh(1); + + private static final int BYTE_GRAY_LCMS_FMT = + colorspaceSh(PT_GRAY)| + channelsSh(1)| + bytesSh(1); + + private static final int USHORT_GRAY_LCMS_FMT = + colorspaceSh(PT_GRAY)| + channelsSh(1)| + bytesSh(2); + + // LCMS format packed into 32 bit value. For description + // of this format refer to LCMS documentation. + private int cmmFormat = 0; + + // Dimensions + private int rows = 0; + private int cols = 0; + + // Scanline may contain some padding in the end + private int scanlineStride = -1; + + private Object imageData; + // It's possible to have offset from the beginning of the array + private int dataOffset; + + // Has the image alpha channel? If has - here its band band offset goes + private int alphaOffset = -1; + + // initializes proper field IDs + private static native void initIDs(); + + static { + NativeCMM.loadCMM(); + initIDs(); + } + + //////////////////////////////////// + // LCMS image format encoders + //////////////////////////////////// + private static int colorspaceSh(int s) { + return (s << 16); + } + + private static int swapfirstSh(int s) { + return (s << 14); + } + + private static int flavorSh(int s) { + return (s << 13); + } + + private static int planarSh(int s) { + return (s << 12); + } + + private static int endianSh(int s) { + return (s << 11); + } + + private static int doswapSh(int s) { + return (s << 10); + } + + private static int extraSh(int s) { + return (s << 7); + } + + private static int channelsSh(int s) { + return (s << 3); + } + + private static int bytesSh(int s) { + return s; + } + //////////////////////////////////// + // End of LCMS image format encoders + //////////////////////////////////// + + // Accessors + Object getChannelData() { + return imageData; + } + + int getNumCols() { + return cols; + } + + int getNumRows() { + return rows; + } + + // Constructors + public NativeImageFormat() { + } + + /** + * Simple image layout for common case with + * not optimized workflow. + * + * For hifi colorspaces with 5+ color channels imgData + * should be byte array. + * + * For common colorspaces with up to 4 color channels it + * should be short array. + * + * Alpha channel is handled by caller, not by CMS. + * + * Color channels are in their natural order (not BGR but RGB). + * + * @param imgData - array of byte or short + * @param nChannels - number of channels + * @param nRows - number of scanlines in the image + * @param nCols - number of pixels in one row of the image + */ + public NativeImageFormat(Object imgData, int nChannels, int nRows, int nCols) { + if (imgData instanceof short[]) { + cmmFormat |= bytesSh(2); + } + else if (imgData instanceof byte[]) { + cmmFormat |= bytesSh(1); + } + else + // awt.47=First argument should be byte or short array + throw new IllegalArgumentException(Messages.getString("awt.47")); //$NON-NLS-1$ + + cmmFormat |= channelsSh(nChannels); + + rows = nRows; + cols = nCols; + + imageData = imgData; + + dataOffset = 0; + } + + /** + * Deduces image format from the buffered image type + * or color and sample models. + * @param bi - image + * @return image format object + */ + public static NativeImageFormat createNativeImageFormat(BufferedImage bi) { + NativeImageFormat fmt = new NativeImageFormat(); + + switch (bi.getType()) { + case BufferedImage.TYPE_INT_RGB: { + fmt.cmmFormat = INT_RGB_LCMS_FMT; + break; + } + + case BufferedImage.TYPE_INT_ARGB: + case BufferedImage.TYPE_INT_ARGB_PRE: { + fmt.cmmFormat = INT_ARGB_LCMS_FMT; + fmt.alphaOffset = 3; + break; + } + + case BufferedImage.TYPE_INT_BGR: { + fmt.cmmFormat = INT_BGR_LCMS_FMT; + break; + } + + case BufferedImage.TYPE_3BYTE_BGR: { + fmt.cmmFormat = THREE_BYTE_BGR_LCMS_FMT; + break; + } + + case BufferedImage.TYPE_4BYTE_ABGR_PRE: + case BufferedImage.TYPE_4BYTE_ABGR: { + fmt.cmmFormat = FOUR_BYTE_ABGR_LCMS_FMT; + fmt.alphaOffset = 0; + break; + } + + case BufferedImage.TYPE_BYTE_GRAY: { + fmt.cmmFormat = BYTE_GRAY_LCMS_FMT; + break; + } + + case BufferedImage.TYPE_USHORT_GRAY: { + fmt.cmmFormat = USHORT_GRAY_LCMS_FMT; + break; + } + + case BufferedImage.TYPE_BYTE_BINARY: + case BufferedImage.TYPE_USHORT_565_RGB: + case BufferedImage.TYPE_USHORT_555_RGB: + case BufferedImage.TYPE_BYTE_INDEXED: { + // A bunch of unsupported formats + return null; + } + + default: + break; // Try to look at sample model and color model + } + + + if (fmt.cmmFormat == 0) { + ColorModel cm = bi.getColorModel(); + SampleModel sm = bi.getSampleModel(); + + if (sm instanceof ComponentSampleModel) { + ComponentSampleModel csm = (ComponentSampleModel) sm; + fmt.cmmFormat = getFormatFromComponentModel(csm, cm.hasAlpha()); + fmt.scanlineStride = calculateScanlineStrideCSM(csm, bi.getRaster()); + } else if (sm instanceof SinglePixelPackedSampleModel) { + SinglePixelPackedSampleModel sppsm = (SinglePixelPackedSampleModel) sm; + fmt.cmmFormat = getFormatFromSPPSampleModel(sppsm, cm.hasAlpha()); + fmt.scanlineStride = calculateScanlineStrideSPPSM(sppsm, bi.getRaster()); + } + + if (cm.hasAlpha()) + fmt.alphaOffset = calculateAlphaOffset(sm, bi.getRaster()); + } + + if (fmt.cmmFormat == 0) + return null; + + if (!fmt.setImageData(bi.getRaster().getDataBuffer())) { + return null; + } + + fmt.rows = bi.getHeight(); + fmt.cols = bi.getWidth(); + + fmt.dataOffset = bi.getRaster().getDataBuffer().getOffset(); + + return fmt; + } + + /** + * Deduces image format from the raster sample model. + * @param r - raster + * @return image format object + */ + public static NativeImageFormat createNativeImageFormat(Raster r) { + NativeImageFormat fmt = new NativeImageFormat(); + SampleModel sm = r.getSampleModel(); + + // Assume that there's no alpha + if (sm instanceof ComponentSampleModel) { + ComponentSampleModel csm = (ComponentSampleModel) sm; + fmt.cmmFormat = getFormatFromComponentModel(csm, false); + fmt.scanlineStride = calculateScanlineStrideCSM(csm, r); + } else if (sm instanceof SinglePixelPackedSampleModel) { + SinglePixelPackedSampleModel sppsm = (SinglePixelPackedSampleModel) sm; + fmt.cmmFormat = getFormatFromSPPSampleModel(sppsm, false); + fmt.scanlineStride = calculateScanlineStrideSPPSM(sppsm, r); + } + + if (fmt.cmmFormat == 0) + return null; + + fmt.cols = r.getWidth(); + fmt.rows = r.getHeight(); + fmt.dataOffset = r.getDataBuffer().getOffset(); + + if (!fmt.setImageData(r.getDataBuffer())) + return null; + + return fmt; + } + + /** + * Obtains LCMS format from the component sample model + * @param sm - sample model + * @param hasAlpha - true if there's an alpha channel + * @return LCMS format + */ + private static int getFormatFromComponentModel(ComponentSampleModel sm, boolean hasAlpha) { + // Multiple data arrays (banks) not supported + int bankIndex = sm.getBankIndices()[0]; + for (int i=1; i < sm.getNumBands(); i++) { + if (sm.getBankIndices()[i] != bankIndex) { + return 0; + } + } + + int channels = hasAlpha ? sm.getNumBands()-1 : sm.getNumBands(); + int extra = hasAlpha ? 1 : 0; + int bytes = 1; + switch (sm.getDataType()) { + case DataBuffer.TYPE_BYTE: + bytes = 1; break; + case DataBuffer.TYPE_SHORT: + case DataBuffer.TYPE_USHORT: + bytes = 2; break; + case DataBuffer.TYPE_INT: + bytes = 4; break; + case DataBuffer.TYPE_DOUBLE: + bytes = 0; break; + default: + return 0; // Unsupported data type + } + + int doSwap = 0; + int swapFirst = 0; + boolean knownFormat = false; + + int i; + + // "RGBA" + for (i=0; i < sm.getNumBands(); i++) { + if (sm.getBandOffsets()[i] != i) break; + } + if (i == sm.getNumBands()) { // Ok, it is it + doSwap = 0; + swapFirst = 0; + knownFormat = true; + } + + // "ARGB" + if (!knownFormat) { + for (i=0; i < sm.getNumBands()-1; i++) { + if (sm.getBandOffsets()[i] != i+1) break; + } + if (sm.getBandOffsets()[i] == 0) i++; + if (i == sm.getNumBands()) { // Ok, it is it + doSwap = 0; + swapFirst = 1; + knownFormat = true; + } + } + + // "BGRA" + if (!knownFormat) { + for (i=0; i < sm.getNumBands()-1; i++) { + if (sm.getBandOffsets()[i] != sm.getNumBands() - 2 - i) break; + } + if (sm.getBandOffsets()[i] == sm.getNumBands()-1) i++; + if (i == sm.getNumBands()) { // Ok, it is it + doSwap = 1; + swapFirst = 1; + knownFormat = true; + } + } + + // "ABGR" + if (!knownFormat) { + for (i=0; i < sm.getNumBands(); i++) { + if (sm.getBandOffsets()[i] != sm.getNumBands() - 1 - i) break; + } + if (i == sm.getNumBands()) { // Ok, it is it + doSwap = 1; + swapFirst = 0; + knownFormat = true; + } + } + + // XXX - Planar formats are not supported yet + if (!knownFormat) + return 0; + + return + channelsSh(channels) | + bytesSh(bytes) | + extraSh(extra) | + doswapSh(doSwap) | + swapfirstSh(swapFirst); + } + + /** + * Obtains LCMS format from the single pixel packed sample model + * @param sm - sample model + * @param hasAlpha - true if there's an alpha channel + * @return LCMS format + */ + private static int getFormatFromSPPSampleModel(SinglePixelPackedSampleModel sm, + boolean hasAlpha) { + // Can we extract bytes? + int mask = sm.getBitMasks()[0] >>> sm.getBitOffsets()[0]; + if (!(mask == 0xFF || mask == 0xFFFF || mask == 0xFFFFFFFF)) + return 0; + + // All masks are same? + for (int i = 1; i < sm.getNumBands(); i++) { + if ((sm.getBitMasks()[i] >>> sm.getBitOffsets()[i]) != mask) + return 0; + } + + int pixelSize = 0; + // Check if data type is supported + if (sm.getDataType() == DataBuffer.TYPE_USHORT) + pixelSize = 2; + else if (sm.getDataType() == DataBuffer.TYPE_INT) + pixelSize = 4; + else + return 0; + + + int bytes = 0; + switch (mask) { + case 0xFF: + bytes = 1; + break; + case 0xFFFF: + bytes = 2; + break; + case 0xFFFFFFFF: + bytes = 4; + break; + default: return 0; + } + + + int channels = hasAlpha ? sm.getNumBands()-1 : sm.getNumBands(); + int extra = hasAlpha ? 1 : 0; + extra += pixelSize/bytes - sm.getNumBands(); // Unused bytes? + + // Form an ArrayList containing offset for each band + ArrayList offsetsLst = new ArrayList(); + for (int k=0; k < sm.getNumBands(); k++) { + offsetsLst.add(new Integer(sm.getBitOffsets()[k]/(bytes*8))); + } + + // Add offsets for unused space + for (int i=0; i + * + * xlfd format: + * -Foundry-Family-Weight-Slant-Width-Style-PixelSize-PointSize-ResX-ResY-Spacing-AvgWidth-Registry-Encoding + * @param xlfd String parameter in xlfd format + */ + public static String[] parseXLFD(String xlfd){ + int fieldsCount = 14; + String fieldsDelim = "-"; //$NON-NLS-1$ + String[] res = new String[fieldsCount]; + if (!xlfd.startsWith(fieldsDelim)){ + return null; + } + + xlfd = xlfd.substring(1); + int i=0; + int pos; + for (i=0; i < fieldsCount-1; i++){ + pos = xlfd.indexOf(fieldsDelim); + if (pos != -1){ + res[i] = xlfd.substring(0, pos); + xlfd = xlfd.substring(pos + 1); + } else { + return null; + } + } + pos = xlfd.indexOf(fieldsDelim); + + // check if no fields left + if(pos != -1){ + return null; + } + res[fieldsCount-1] = xlfd; + + return res; + } + + public int getFaceIndex(String faceName){ + + for (int i = 0; i < faces.length; i++) { + if(faces[i].equals(faceName)){ + return i; + } + } + return -1; + } + + public String[] getAllFamilies(){ + if (allFamilies == null){ + allFamilies = new String[]{"sans-serif", "serif", "monospace"}; + } + return allFamilies; + } + + public Font[] getAllFonts(){ + Font[] fonts = new Font[faces.length]; + for (int i =0; i < fonts.length;i++){ + fonts[i] = new Font(faces[i], Font.PLAIN, 1); + } + return fonts; + } + + public FontPeer createPhysicalFontPeer(String name, int style, int size) { + AndroidFont peer; + int familyIndex = getFamilyIndex(name); + if (familyIndex != -1){ + // !! we use family names from the list with cached families because + // they are differ from the family names in xlfd structure, in xlfd + // family names mostly in lower case. + peer = new AndroidFont(getFamily(familyIndex), style, size); + peer.setFamily(getFamily(familyIndex)); + return peer; + } + int faceIndex = getFaceIndex(name); + if (faceIndex != -1){ + + peer = new AndroidFont(name, style, size); + return peer; + } + + return null; + } + + public FontPeer createDefaultFont(int style, int size) { + Log.i("DEFAULT FONT", Integer.toString(style)); + return new AndroidFont(DEFAULT_NAME, style, size); + } + +} diff --git a/awt/org/apache/harmony/awt/gl/font/AndroidFontProperty.java b/awt/org/apache/harmony/awt/gl/font/AndroidFontProperty.java new file mode 100644 index 0000000..0cfdc43 --- /dev/null +++ b/awt/org/apache/harmony/awt/gl/font/AndroidFontProperty.java @@ -0,0 +1,81 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Ilya S. Okomin + * @version $Revision$ + * + */ +package org.apache.harmony.awt.gl.font; + +/** + * Android FontProperty implementation, applicable for Linux formats of + * font property files. + */ +public class AndroidFontProperty extends FontProperty { + + /** xlfd string that is applicable for Linux font.properties */ + String xlfd; + + /** logical name of the font corresponding to this FontProperty */ + String logicalName; + + /** style name of the font corresponding to this FontProperty */ + String styleName; + + public AndroidFontProperty(String _logicalName, String _styleName, String _fileName, String _name, String _xlfd, int _style, int[] exclusionRange, String _encoding){ + this.logicalName = _logicalName; + this.styleName = _styleName; + this.name = _name; + this.encoding = _encoding; + this.exclRange = exclusionRange; + this.fileName = _fileName; + this.xlfd = _xlfd; + this.style = _style; + } + + /** + * Returns logical name of the font corresponding to this FontProperty. + */ + public String getLogicalName(){ + return logicalName; + } + + /** + * Returns style name of the font corresponding to this FontProperty. + */ + public String getStyleName(){ + return styleName; + } + + /** + * Returns xlfd string of this FontProperty. + */ + public String getXLFD(){ + return xlfd; + } + + public String toString(){ + return new String(this.getClass().getName() + + "[name=" + name + //$NON-NLS-1$ + ",fileName="+ fileName + //$NON-NLS-1$ + ",Charset=" + encoding + //$NON-NLS-1$ + ",exclRange=" + exclRange + //$NON-NLS-1$ + ",xlfd=" + xlfd + "]"); //$NON-NLS-1$ //$NON-NLS-2$ + + } + +} diff --git a/awt/org/apache/harmony/awt/gl/font/AndroidGlyphVector.java b/awt/org/apache/harmony/awt/gl/font/AndroidGlyphVector.java new file mode 100644 index 0000000..4ce5aed --- /dev/null +++ b/awt/org/apache/harmony/awt/gl/font/AndroidGlyphVector.java @@ -0,0 +1,219 @@ +package org.apache.harmony.awt.gl.font; + +import com.android.internal.awt.AndroidGraphics2D; + +import java.awt.Font; +import java.awt.Shape; +import java.awt.font.FontRenderContext; +import java.awt.font.GlyphJustificationInfo; +import java.awt.font.GlyphMetrics; +import java.awt.font.GlyphVector; +import java.awt.geom.AffineTransform; +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; + +import android.util.Log; +import android.graphics.Path; + +public class AndroidGlyphVector extends GlyphVector { + + // array of chars defined in constructor + public char[] charVector; + + // array of Glyph objects, that describe information about glyphs + public Glyph[] vector; + + // array of default positions of glyphs in GlyphVector + // without applying GlyphVector's transform + float[] defaultPositions; + + // array of logical positions of glyphs in GlyphVector + + float[] logicalPositions; + + // array of visual (real) positions of glyphs in GlyphVector + public float[] visualPositions; + + // FontRenderContext for this vector. + protected FontRenderContext vectorFRC; + + // layout flags mask + protected int layoutFlags = 0; + + // array of cached glyph outlines + protected Shape[] gvShapes; + + FontPeerImpl peer; + + // font corresponding to the GlyphVector + Font font; + + // ascent of the font + float ascent; + + // height of the font + float height; + + // leading of the font + float leading; + + // descent of the font + float descent; + + // transform of the GlyphVector + AffineTransform transform; + + @SuppressWarnings("deprecation") + public AndroidGlyphVector(char[] chars, FontRenderContext frc, Font fnt, + int flags) { + int len = chars.length; + this.font = fnt; + LineMetricsImpl lmImpl = (LineMetricsImpl)fnt.getLineMetrics(String.valueOf(chars), frc); + this.ascent = lmImpl.getAscent(); + this.height = lmImpl.getHeight(); + this.leading = lmImpl.getLeading(); + this.descent = lmImpl.getDescent(); + this.charVector = chars; + this.vectorFRC = frc; + } + + public AndroidGlyphVector(char[] chars, FontRenderContext frc, Font fnt) { + this(chars, frc, fnt, 0); + } + + public AndroidGlyphVector(String str, FontRenderContext frc, Font fnt) { + this(str.toCharArray(), frc, fnt, 0); + } + + public AndroidGlyphVector(String str, FontRenderContext frc, Font fnt, int flags) { + this(str.toCharArray(), frc, fnt, flags); + } + + @Override + public boolean equals(GlyphVector glyphVector) { + return false; + } + + public char[] getGlyphs() { + return this.charVector; + } + + @Override + public Font getFont() { + return this.font; + } + + @Override + public FontRenderContext getFontRenderContext() { + return this.vectorFRC; + } + + @Override + public int getGlyphCode(int glyphIndex) { + return charVector[glyphIndex]; + } + + @Override + public int[] getGlyphCodes(int beginGlyphIndex, int numEntries, + int[] codeReturn) { + throw new RuntimeException("Not implemented!"); + } + + @Override + public GlyphJustificationInfo getGlyphJustificationInfo(int glyphIndex) { + throw new RuntimeException("Not implemented!"); + } + + @Override + public Shape getGlyphLogicalBounds(int glyphIndex) { + throw new RuntimeException("Not implemented!"); + } + + @Override + public GlyphMetrics getGlyphMetrics(int glyphIndex) { + throw new RuntimeException("Not implemented!"); + } + + public Path getAndroidGlyphOutline(int glyphIndex) { + AndroidGraphics2D g = AndroidGraphics2D.getInstance(); + Path path = new Path(); + char tmp[] = new char[1]; + tmp[0] = charVector[glyphIndex]; + ((AndroidGraphics2D)g).getAndroidPaint().getTextPath(new String(tmp), 0, 1, 0, 0, path); + return path; + } + + @Override + public Shape getGlyphOutline(int glyphIndex) { + throw new RuntimeException("Not implemented!"); + } + + @Override + public Point2D getGlyphPosition(int glyphIndex) { + throw new RuntimeException("Not implemented!"); + } + + @Override + public float[] getGlyphPositions(int beginGlyphIndex, int numEntries, + float[] positionReturn) { + throw new RuntimeException("Not implemented!"); + } + + @Override + public AffineTransform getGlyphTransform(int glyphIndex) { + throw new RuntimeException("Not implemented!"); + } + + @Override + public Shape getGlyphVisualBounds(int glyphIndex) { + throw new RuntimeException("Not implemented!"); + } + + @Override + public Rectangle2D getLogicalBounds() { + throw new RuntimeException("Not implemented!"); + } + + @Override + public int getNumGlyphs() { + return charVector.length; + } + + @Override + public Shape getOutline(float x, float y) { + throw new RuntimeException("Not implemented!"); + } + + @Override + public Shape getOutline() { + throw new RuntimeException("Not implemented!"); + } + + public Path getAndroidOutline() { + AndroidGraphics2D g = AndroidGraphics2D.getInstance(); + Path path = new Path(); + ((AndroidGraphics2D)g).getAndroidPaint().getTextPath(new String(charVector), 0, charVector.length, 0, 0, path); + return path; + } + + @Override + public Rectangle2D getVisualBounds() { + throw new RuntimeException("Not implemented!"); + } + + @Override + public void performDefaultLayout() { + throw new RuntimeException("Not implemented!"); + } + + @Override + public void setGlyphPosition(int glyphIndex, Point2D newPos) { + throw new RuntimeException("Not implemented!"); + } + + @Override + public void setGlyphTransform(int glyphIndex, AffineTransform trans) { + throw new RuntimeException("Not implemented!"); + } + +} diff --git a/awt/org/apache/harmony/awt/gl/font/AndroidLineMetrics.java b/awt/org/apache/harmony/awt/gl/font/AndroidLineMetrics.java new file mode 100644 index 0000000..f37be6d --- /dev/null +++ b/awt/org/apache/harmony/awt/gl/font/AndroidLineMetrics.java @@ -0,0 +1,120 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Ilya S. Okomin + * @version $Revision$ + */ +package org.apache.harmony.awt.gl.font; + +import java.awt.font.FontRenderContext; +import org.apache.harmony.awt.gl.font.LineMetricsImpl; + + +/** + * + * Linux implementation of LineMetrics class + */ +public class AndroidLineMetrics extends LineMetricsImpl { + + /** + * Constructor + */ + public AndroidLineMetrics( AndroidFont fnt, + FontRenderContext frc, + String str){ + numChars = str.length(); + baseLineIndex = 0; + + ascent = fnt.ascent; // Ascent of the font + descent = -fnt.descent; // Descent of the font + leading = fnt.leading; // External leading + + height = ascent + descent + leading; // Height of the font ( == (ascent + descent + leading)) + underlineThickness = 0.0f; + underlineOffset = 0.0f; + strikethroughThickness = 0.0f; + strikethroughOffset = 0.0f; + maxCharWidth = 0.0f; + + // TODO: Find out pixel metrics + /* + * positive metrics rounded to the smallest int that is bigger than value + * negative metrics rounded to the smallest int that is lesser than value + * thicknesses rounded to int ((int)round(value + 0.5)) + * + */ + + lAscent = (int)Math.ceil(fnt.ascent);// // Ascent of the font + lDescent = -(int)Math.ceil(fnt.descent);// Descent of the font + lLeading = (int)Math.ceil(leading); // External leading + + lHeight = lAscent + lDescent + lLeading; // Height of the font ( == (ascent + descent + leading)) + + lUnderlineThickness = Math.round(underlineThickness);//(int)metrics[11]; + + if (underlineOffset >= 0){ + lUnderlineOffset = (int)Math.ceil(underlineOffset); + } else { + lUnderlineOffset = (int)Math.floor(underlineOffset); + } + + lStrikethroughThickness = Math.round(strikethroughThickness); //(int)metrics[13]; + + if (strikethroughOffset >= 0){ + lStrikethroughOffset = (int)Math.ceil(strikethroughOffset); + } else { + lStrikethroughOffset = (int)Math.floor(strikethroughOffset); + } + + lMaxCharWidth = (int)Math.ceil(maxCharWidth); //(int)metrics[15]; + units_per_EM = 0; + + } + + public float[] getBaselineOffsets() { + // TODO: implement baseline offsets for TrueType fonts + if (baselineOffsets == null){ + float[] baselineData = null; + + // Temporary workaround: + // Commented out native data initialization, since it can + // cause failures with opening files in multithreaded applications. + // + // TODO: support work with truetype data in multithreaded + // applications. + + // If font TrueType data is taken from BASE table +// if ((this.font.getFontHandle() != 0) && (font.getFontType() == FontManager.FONT_TYPE_TT)){ +// baselineData = LinuxNativeFont.getBaselineOffsetsNative(font.getFontHandle(), font.getSize(), ascent, descent, units_per_EM); +// } +// + baseLineIndex = 0; + baselineOffsets = new float[]{0, (-ascent+descent)/2, -ascent}; + } + + return baselineOffsets; + } + + public int getBaselineIndex() { + if (baselineOffsets == null){ + // get offsets and set correct index + getBaselineOffsets(); + } + return baseLineIndex; + } + +} diff --git a/awt/org/apache/harmony/awt/gl/font/BasicMetrics.java b/awt/org/apache/harmony/awt/gl/font/BasicMetrics.java new file mode 100644 index 0000000..c0fb390 --- /dev/null +++ b/awt/org/apache/harmony/awt/gl/font/BasicMetrics.java @@ -0,0 +1,134 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Oleg V. Khaschansky + * @version $Revision$ + * + */ + +package org.apache.harmony.awt.gl.font; + +import java.awt.font.LineMetrics; +import java.awt.font.GraphicAttribute; +import java.awt.*; + +/** + * Date: May 14, 2005 + * Time: 7:44:13 PM + * + * This class incapsulates text metrics specific for the text layout or + * for the separate text segment. Text segment is a text run with the constant direction + * and attributes like font, decorations, etc. BasicMetrics is also used to store + * calculated text metrics like advance, ascent or descent. this class is very similar to + * LineMetrics, but provides some additional info, constructors and is more transparent. + */ +public class BasicMetrics { + int baseLineIndex; + + float ascent; // Ascent of the font + float descent; // Descent of the font + float leading; // External leading + float advance; + + float italicAngle; + float superScriptOffset; + + float underlineOffset; + float underlineThickness; + + float strikethroughOffset; + float strikethroughThickness; + + /** + * Constructs BasicMetrics from LineMetrics and font + * @param lm + * @param font + */ + BasicMetrics(LineMetrics lm, Font font) { + ascent = lm.getAscent(); + descent = lm.getDescent(); + leading = lm.getLeading(); + + underlineOffset = lm.getUnderlineOffset(); + underlineThickness = lm.getUnderlineThickness(); + + strikethroughOffset = lm.getStrikethroughOffset(); + strikethroughThickness = lm.getStrikethroughThickness(); + + baseLineIndex = lm.getBaselineIndex(); + + italicAngle = font.getItalicAngle(); + superScriptOffset = (float) font.getTransform().getTranslateY(); + } + + /** + * Constructs BasicMetrics from GraphicAttribute. + * It gets ascent and descent from the graphic attribute and + * computes reasonable defaults for other metrics. + * @param ga - graphic attribute + */ + BasicMetrics(GraphicAttribute ga) { + ascent = ga.getAscent(); + descent = ga.getDescent(); + leading = 2; + + baseLineIndex = ga.getAlignment(); + + italicAngle = 0; + superScriptOffset = 0; + + underlineOffset = Math.max(descent/2, 1); + + // Just suggested, should be cap_stem_width or something like that + underlineThickness = Math.max(ascent/13, 1); + + strikethroughOffset = -ascent/2; // Something like middle of the line + strikethroughThickness = underlineThickness; + } + + /** + * Copies metrics from the TextMetricsCalculator object. + * @param tmc - TextMetricsCalculator object + */ + BasicMetrics(TextMetricsCalculator tmc) { + ascent = tmc.ascent; + descent = tmc.descent; + leading = tmc.leading; + advance = tmc.advance; + baseLineIndex = tmc.baselineIndex; + } + + public float getAscent() { + return ascent; + } + + public float getDescent() { + return descent; + } + + public float getLeading() { + return leading; + } + + public float getAdvance() { + return advance; + } + + public int getBaseLineIndex() { + return baseLineIndex; + } +} diff --git a/awt/org/apache/harmony/awt/gl/font/CaretManager.java b/awt/org/apache/harmony/awt/gl/font/CaretManager.java new file mode 100644 index 0000000..b18bdd5 --- /dev/null +++ b/awt/org/apache/harmony/awt/gl/font/CaretManager.java @@ -0,0 +1,530 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Oleg V. Khaschansky + * @version $Revision$ + * + * @date: Jun 14, 2005 + */ + +package org.apache.harmony.awt.gl.font; + +import java.awt.font.TextHitInfo; +import java.awt.font.TextLayout; +import java.awt.geom.Rectangle2D; +import java.awt.geom.GeneralPath; +import java.awt.geom.Line2D; +import java.awt.*; + +import org.apache.harmony.awt.internal.nls.Messages; + +/** + * This class provides functionality for creating caret and highlight shapes + * (bidirectional text is also supported, but, unfortunately, not tested yet). + */ +public class CaretManager { + private TextRunBreaker breaker; + + public CaretManager(TextRunBreaker breaker) { + this.breaker = breaker; + } + + /** + * Checks if TextHitInfo is not out of the text range and throws the + * IllegalArgumentException if it is. + * @param info - text hit info + */ + private void checkHit(TextHitInfo info) { + int idx = info.getInsertionIndex(); + + if (idx < 0 || idx > breaker.getCharCount()) { + // awt.42=TextHitInfo out of range + throw new IllegalArgumentException(Messages.getString("awt.42")); //$NON-NLS-1$ + } + } + + /** + * Calculates and returns visual position from the text hit info. + * @param hitInfo - text hit info + * @return visual index + */ + private int getVisualFromHitInfo(TextHitInfo hitInfo) { + final int idx = hitInfo.getCharIndex(); + + if (idx >= 0 && idx < breaker.getCharCount()) { + int visual = breaker.getVisualFromLogical(idx); + // We take next character for (LTR char + TRAILING info) and (RTL + LEADING) + if (hitInfo.isLeadingEdge() ^ ((breaker.getLevel(idx) & 0x1) == 0x0)) { + visual++; + } + return visual; + } else if (idx < 0) { + return breaker.isLTR() ? 0: breaker.getCharCount(); + } else { + return breaker.isLTR() ? breaker.getCharCount() : 0; + } + } + + /** + * Calculates text hit info from the visual position + * @param visual - visual position + * @return text hit info + */ + private TextHitInfo getHitInfoFromVisual(int visual) { + final boolean first = visual == 0; + + if (!(first || visual == breaker.getCharCount())) { + int logical = breaker.getLogicalFromVisual(visual); + return (breaker.getLevel(logical) & 0x1) == 0x0 ? + TextHitInfo.leading(logical) : // LTR + TextHitInfo.trailing(logical); // RTL + } else if (first) { + return breaker.isLTR() ? + TextHitInfo.trailing(-1) : + TextHitInfo.leading(breaker.getCharCount()); + } else { // Last + return breaker.isLTR() ? + TextHitInfo.leading(breaker.getCharCount()) : + TextHitInfo.trailing(-1); + } + } + + /** + * Creates caret info. Required for the getCaretInfo + * methods of the TextLayout + * @param hitInfo - specifies caret position + * @return caret info, see TextLayout.getCaretInfo documentation + */ + public float[] getCaretInfo(TextHitInfo hitInfo) { + checkHit(hitInfo); + float res[] = new float[2]; + + int visual = getVisualFromHitInfo(hitInfo); + float advance, angle; + TextRunSegment seg; + + if (visual < breaker.getCharCount()) { + int logIdx = breaker.getLogicalFromVisual(visual); + int segmentIdx = breaker.logical2segment[logIdx]; + seg = breaker.runSegments.get(segmentIdx); + advance = seg.x + seg.getAdvanceDelta(seg.getStart(), logIdx); + angle = seg.metrics.italicAngle; + + } else { // Last character + int logIdx = breaker.getLogicalFromVisual(visual-1); + int segmentIdx = breaker.logical2segment[logIdx]; + seg = breaker.runSegments.get(segmentIdx); + advance = seg.x + seg.getAdvanceDelta(seg.getStart(), logIdx+1); + } + + angle = seg.metrics.italicAngle; + + res[0] = advance; + res[1] = angle; + + return res; + } + + /** + * Returns the next position to the right from the current caret position + * @param hitInfo - current position + * @return next position to the right + */ + public TextHitInfo getNextRightHit(TextHitInfo hitInfo) { + checkHit(hitInfo); + int visual = getVisualFromHitInfo(hitInfo); + + if (visual == breaker.getCharCount()) { + return null; + } + + TextHitInfo newInfo; + + while(visual <= breaker.getCharCount()) { + visual++; + newInfo = getHitInfoFromVisual(visual); + + if (newInfo.getCharIndex() >= breaker.logical2segment.length) { + return newInfo; + } + + if (hitInfo.getCharIndex() >= 0) { // Don't check for leftmost info + if ( + breaker.logical2segment[newInfo.getCharIndex()] != + breaker.logical2segment[hitInfo.getCharIndex()] + ) { + return newInfo; // We crossed segment boundary + } + } + + TextRunSegment seg = breaker.runSegments.get(breaker.logical2segment[newInfo + .getCharIndex()]); + if (!seg.charHasZeroAdvance(newInfo.getCharIndex())) { + return newInfo; + } + } + + return null; + } + + /** + * Returns the next position to the left from the current caret position + * @param hitInfo - current position + * @return next position to the left + */ + public TextHitInfo getNextLeftHit(TextHitInfo hitInfo) { + checkHit(hitInfo); + int visual = getVisualFromHitInfo(hitInfo); + + if (visual == 0) { + return null; + } + + TextHitInfo newInfo; + + while(visual >= 0) { + visual--; + newInfo = getHitInfoFromVisual(visual); + + if (newInfo.getCharIndex() < 0) { + return newInfo; + } + + // Don't check for rightmost info + if (hitInfo.getCharIndex() < breaker.logical2segment.length) { + if ( + breaker.logical2segment[newInfo.getCharIndex()] != + breaker.logical2segment[hitInfo.getCharIndex()] + ) { + return newInfo; // We crossed segment boundary + } + } + + TextRunSegment seg = breaker.runSegments.get(breaker.logical2segment[newInfo + .getCharIndex()]); + if (!seg.charHasZeroAdvance(newInfo.getCharIndex())) { + return newInfo; + } + } + + return null; + } + + /** + * For each visual caret position there are two hits. For the simple LTR text one is + * a trailing of the previous char and another is the leading of the next char. This + * method returns the opposite hit for the given hit. + * @param hitInfo - given hit + * @return opposite hit + */ + public TextHitInfo getVisualOtherHit(TextHitInfo hitInfo) { + checkHit(hitInfo); + + int idx = hitInfo.getCharIndex(); + + int resIdx; + boolean resIsLeading; + + if (idx >= 0 && idx < breaker.getCharCount()) { // Hit info in the middle + int visual = breaker.getVisualFromLogical(idx); + + // Char is LTR + LEADING info + if (((breaker.getLevel(idx) & 0x1) == 0x0) ^ hitInfo.isLeadingEdge()) { + visual++; + if (visual == breaker.getCharCount()) { + if (breaker.isLTR()) { + resIdx = breaker.getCharCount(); + resIsLeading = true; + } else { + resIdx = -1; + resIsLeading = false; + } + } else { + resIdx = breaker.getLogicalFromVisual(visual); + if ((breaker.getLevel(resIdx) & 0x1) == 0x0) { + resIsLeading = true; + } else { + resIsLeading = false; + } + } + } else { + visual--; + if (visual == -1) { + if (breaker.isLTR()) { + resIdx = -1; + resIsLeading = false; + } else { + resIdx = breaker.getCharCount(); + resIsLeading = true; + } + } else { + resIdx = breaker.getLogicalFromVisual(visual); + if ((breaker.getLevel(resIdx) & 0x1) == 0x0) { + resIsLeading = false; + } else { + resIsLeading = true; + } + } + } + } else if (idx < 0) { // before "start" + if (breaker.isLTR()) { + resIdx = breaker.getLogicalFromVisual(0); + resIsLeading = (breaker.getLevel(resIdx) & 0x1) == 0x0; // LTR char? + } else { + resIdx = breaker.getLogicalFromVisual(breaker.getCharCount() - 1); + resIsLeading = (breaker.getLevel(resIdx) & 0x1) != 0x0; // RTL char? + } + } else { // idx == breaker.getCharCount() + if (breaker.isLTR()) { + resIdx = breaker.getLogicalFromVisual(breaker.getCharCount() - 1); + resIsLeading = (breaker.getLevel(resIdx) & 0x1) != 0x0; // LTR char? + } else { + resIdx = breaker.getLogicalFromVisual(0); + resIsLeading = (breaker.getLevel(resIdx) & 0x1) == 0x0; // RTL char? + } + } + + return resIsLeading ? TextHitInfo.leading(resIdx) : TextHitInfo.trailing(resIdx); + } + + public Line2D getCaretShape(TextHitInfo hitInfo, TextLayout layout) { + return getCaretShape(hitInfo, layout, true, false, null); + } + + /** + * Creates a caret shape. + * @param hitInfo - hit where to place a caret + * @param layout - text layout + * @param useItalic - unused for now, was used to create + * slanted carets for italic text + * @param useBounds - true if the cared should fit into the provided bounds + * @param bounds - bounds for the caret + * @return caret shape + */ + public Line2D getCaretShape( + TextHitInfo hitInfo, TextLayout layout, + boolean useItalic, boolean useBounds, Rectangle2D bounds + ) { + checkHit(hitInfo); + + float x1, x2, y1, y2; + + int charIdx = hitInfo.getCharIndex(); + + if (charIdx >= 0 && charIdx < breaker.getCharCount()) { + TextRunSegment segment = breaker.runSegments.get(breaker.logical2segment[charIdx]); + y1 = segment.metrics.descent; + y2 = - segment.metrics.ascent - segment.metrics.leading; + + x1 = x2 = segment.getCharPosition(charIdx) + (hitInfo.isLeadingEdge() ? + 0 : segment.getCharAdvance(charIdx)); + // Decided that straight cursor looks better even for italic fonts, + // especially combined with highlighting + /* + // Not graphics, need to check italic angle and baseline + if (layout.getBaseline() >= 0) { + if (segment.metrics.italicAngle != 0 && useItalic) { + x1 -= segment.metrics.italicAngle * segment.metrics.descent; + x2 += segment.metrics.italicAngle * + (segment.metrics.ascent + segment.metrics.leading); + + float baselineOffset = + layout.getBaselineOffsets()[layout.getBaseline()]; + y1 += baselineOffset; + y2 += baselineOffset; + } + } + */ + } else { + y1 = layout.getDescent(); + y2 = - layout.getAscent() - layout.getLeading(); + x1 = x2 = ((breaker.getBaseLevel() & 0x1) == 0 ^ charIdx < 0) ? + layout.getAdvance() : 0; + } + + if (useBounds) { + y1 = (float) bounds.getMaxY(); + y2 = (float) bounds.getMinY(); + + if (x2 > bounds.getMaxX()) { + x1 = x2 = (float) bounds.getMaxX(); + } + if (x1 < bounds.getMinX()) { + x1 = x2 = (float) bounds.getMinX(); + } + } + + return new Line2D.Float(x1, y1, x2, y2); + } + + /** + * Creates caret shapes for the specified offset. On the boundaries where + * the text is changing its direction this method may return two shapes + * for the strong and the weak carets, in other cases it would return one. + * @param offset - offset in the text. + * @param bounds - bounds to fit the carets into + * @param policy - caret policy + * @param layout - text layout + * @return one or two caret shapes + */ + public Shape[] getCaretShapes( + int offset, Rectangle2D bounds, + TextLayout.CaretPolicy policy, TextLayout layout + ) { + TextHitInfo hit1 = TextHitInfo.afterOffset(offset); + TextHitInfo hit2 = getVisualOtherHit(hit1); + + Shape caret1 = getCaretShape(hit1, layout); + + if (getVisualFromHitInfo(hit1) == getVisualFromHitInfo(hit2)) { + return new Shape[] {caret1, null}; + } + Shape caret2 = getCaretShape(hit2, layout); + + TextHitInfo strongHit = policy.getStrongCaret(hit1, hit2, layout); + return strongHit.equals(hit1) ? + new Shape[] {caret1, caret2} : + new Shape[] {caret2, caret1}; + } + + /** + * Connects two carets to produce a highlight shape. + * @param caret1 - 1st caret + * @param caret2 - 2nd caret + * @return highlight shape + */ + GeneralPath connectCarets(Line2D caret1, Line2D caret2) { + GeneralPath path = new GeneralPath(GeneralPath.WIND_NON_ZERO); + path.moveTo((float) caret1.getX1(), (float) caret1.getY1()); + path.lineTo((float) caret2.getX1(), (float) caret2.getY1()); + path.lineTo((float) caret2.getX2(), (float) caret2.getY2()); + path.lineTo((float) caret1.getX2(), (float) caret1.getY2()); + + path.closePath(); + + return path; + } + + /** + * Creates a highlight shape from given two hits. This shape + * will always be visually contiguous + * @param hit1 - 1st hit + * @param hit2 - 2nd hit + * @param bounds - bounds to fit the shape into + * @param layout - text layout + * @return highlight shape + */ + public Shape getVisualHighlightShape( + TextHitInfo hit1, TextHitInfo hit2, + Rectangle2D bounds, TextLayout layout + ) { + checkHit(hit1); + checkHit(hit2); + + Line2D caret1 = getCaretShape(hit1, layout, false, true, bounds); + Line2D caret2 = getCaretShape(hit2, layout, false, true, bounds); + + return connectCarets(caret1, caret2); + } + + /** + * Suppose that the user visually selected a block of text which has + * several different levels (mixed RTL and LTR), so, in the logical + * representation of the text this selection may be not contigous. + * This methods returns a set of logical ranges for the arbitrary + * visual selection represented by two hits. + * @param hit1 - 1st hit + * @param hit2 - 2nd hit + * @return logical ranges for the selection + */ + public int[] getLogicalRangesForVisualSelection(TextHitInfo hit1, TextHitInfo hit2) { + checkHit(hit1); + checkHit(hit2); + + int visual1 = getVisualFromHitInfo(hit1); + int visual2 = getVisualFromHitInfo(hit2); + + if (visual1 > visual2) { + int tmp = visual2; + visual2 = visual1; + visual1 = tmp; + } + + // Max level is 255, so we don't need more than 512 entries + int results[] = new int[512]; + + int prevLogical, logical, runStart, numRuns = 0; + + logical = runStart = prevLogical = breaker.getLogicalFromVisual(visual1); + + // Get all the runs. We use the fact that direction is constant in all runs. + for (int i=visual1+1; i<=visual2; i++) { + logical = breaker.getLogicalFromVisual(i); + int diff = logical-prevLogical; + + // Start of the next run encountered + if (diff > 1 || diff < -1) { + results[(numRuns)*2] = Math.min(runStart, prevLogical); + results[(numRuns)*2 + 1] = Math.max(runStart, prevLogical); + numRuns++; + runStart = logical; + } + + prevLogical = logical; + } + + // The last unsaved run + results[(numRuns)*2] = Math.min(runStart, logical); + results[(numRuns)*2 + 1] = Math.max(runStart, logical); + numRuns++; + + int retval[] = new int[numRuns*2]; + System.arraycopy(results, 0, retval, 0, numRuns*2); + return retval; + } + + /** + * Creates a highlight shape from given two endpoints in the logical + * representation. This shape is not always visually contiguous + * @param firstEndpoint - 1st logical endpoint + * @param secondEndpoint - 2nd logical endpoint + * @param bounds - bounds to fit the shape into + * @param layout - text layout + * @return highlight shape + */ + public Shape getLogicalHighlightShape( + int firstEndpoint, int secondEndpoint, + Rectangle2D bounds, TextLayout layout + ) { + GeneralPath res = new GeneralPath(); + + for (int i=firstEndpoint; i<=secondEndpoint; i++) { + int endRun = breaker.getLevelRunLimit(i, secondEndpoint); + TextHitInfo hit1 = TextHitInfo.leading(i); + TextHitInfo hit2 = TextHitInfo.trailing(endRun-1); + + Line2D caret1 = getCaretShape(hit1, layout, false, true, bounds); + Line2D caret2 = getCaretShape(hit2, layout, false, true, bounds); + + res.append(connectCarets(caret1, caret2), false); + + i = endRun; + } + + return res; + } +} diff --git a/awt/org/apache/harmony/awt/gl/font/CommonGlyphVector.java b/awt/org/apache/harmony/awt/gl/font/CommonGlyphVector.java new file mode 100644 index 0000000..4040a60 --- /dev/null +++ b/awt/org/apache/harmony/awt/gl/font/CommonGlyphVector.java @@ -0,0 +1,954 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Ilya S. Okomin + * @version $Revision$ + */ +package org.apache.harmony.awt.gl.font; + +import java.awt.Font; +import java.awt.Rectangle; +import java.awt.Shape; +import java.awt.font.FontRenderContext; +import java.awt.font.GlyphJustificationInfo; +import java.awt.font.GlyphMetrics; +import java.awt.font.GlyphVector; +import java.awt.geom.AffineTransform; +import java.awt.geom.GeneralPath; +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; + +import org.apache.harmony.awt.internal.nls.Messages; + +/** + * GlyphVector implementation + */ +public class CommonGlyphVector extends GlyphVector { + + // array of transforms of glyphs in GlyphVector + protected AffineTransform[] glsTransforms; + + // array of chars defined in constructor + public char[] charVector; + + // array of Glyph objects, that describe information about glyphs + public Glyph[] vector; + + // array of default positions of glyphs in GlyphVector + // without applying GlyphVector's transform + float[] defaultPositions; + + // array of logical positions of glyphs in GlyphVector + + float[] logicalPositions; + + // array of visual (real) positions of glyphs in GlyphVector + public float[] visualPositions; + + // FontRenderContext for this vector. + protected FontRenderContext vectorFRC; + + // layout flags mask + protected int layoutFlags = 0; + + // array of cached glyph outlines + protected Shape[] gvShapes; + + FontPeerImpl peer; + + // font corresponding to the GlyphVector + Font font; + + // ascent of the font + float ascent; + + // height of the font + float height; + + // leading of the font + float leading; + + // descent of the font + float descent; + + // transform of the GlyphVector + AffineTransform transform; + + /** + * Creates new CommonGlyphVector object from the specified parameters. + * + * @param chars an array of chars + * @param frc FontRenderContext object + * @param fnt Font object + * @param flags layout flags + */ + @SuppressWarnings("deprecation") + public CommonGlyphVector(char[] chars, FontRenderContext frc, Font fnt, + int flags) { + int len = chars.length; + + this.font = fnt; + this.transform = fnt.getTransform(); + this.peer = (FontPeerImpl) fnt.getPeer(); + + gvShapes = new Shape[len]; + + // !! As pointed in API documentation for the + // getGlyphPosisitions(int index,int numEntries, float[] positionReturn) + // and getGlyphPosition(int index) methods, if the index is equals to + // the number of glyphs the position after the last glyph must be + // returned, thus there are n+1 positions and last (n+1) position + // points to the end of GlyphVector. + + logicalPositions = new float[(len+1)<<1]; + visualPositions = new float[(len+1)<<1]; + defaultPositions = new float[(len+1)<<1]; + + glsTransforms = new AffineTransform[len]; + + this.charVector = chars; + this.vectorFRC = frc; + //LineMetricsImpl lmImpl = (LineMetricsImpl)peer.getLineMetrics(); + + LineMetricsImpl lmImpl = (LineMetricsImpl)fnt.getLineMetrics(String.valueOf(chars), frc); + + this.ascent = lmImpl.getAscent(); + this.height = lmImpl.getHeight(); + this.leading = lmImpl.getLeading(); + this.descent = lmImpl.getDescent(); + this.layoutFlags = flags; + + if ((flags & Font.LAYOUT_RIGHT_TO_LEFT) != 0){ + char vector[] = new char[len]; + for(int i=0; i < len; i++){ + vector[i] = chars[len-i-1]; + } + this.vector = peer.getGlyphs(vector); + + } else { + this.vector = peer.getGlyphs(chars); + } + + this.glsTransforms = new AffineTransform[len]; + + setDefaultPositions(); + performDefaultLayout(); + } + + /** + * Creates new CommonGlyphVector object from the specified parameters. + * Layout flags set to default. + * + * @param chars an array of chars + * @param frc FontRenderContext object + * @param fnt Font object + */ + public CommonGlyphVector(char[] chars, FontRenderContext frc, Font fnt) { + this(chars, frc, fnt, 0); + } + + /** + * Creates new CommonGlyphVector object from the specified parameters. + * Layout flags set to default. + * + * @param str specified string + * @param frc FontRenderContext object + * @param fnt Font object + */ + public CommonGlyphVector(String str, FontRenderContext frc, Font fnt) { + this(str.toCharArray(), frc, fnt, 0); + } + + /** + * Creates new CommonGlyphVector object from the specified parameters. + * + * @param str specified string + * @param frc FontRenderContext object + * @param fnt Font object + * @param flags layout flags + */ + public CommonGlyphVector(String str, FontRenderContext frc, Font fnt, int flags) { + this(str.toCharArray(), frc, fnt, flags); + } + + /** + * Set array of logical positions of the glyphs to + * default with their default advances and height. + */ + void setDefaultPositions(){ + int len = getNumGlyphs(); + + // First [x,y] is set into [0,0] position + // for this reason start index is 1 + for (int i=1; i <= len; i++ ){ + int idx = i << 1; + float advanceX = vector[i-1].getGlyphPointMetrics().getAdvanceX(); + float advanceY = vector[i-1].getGlyphPointMetrics().getAdvanceY(); + + defaultPositions[idx] = defaultPositions[idx-2] + advanceX; + defaultPositions[idx+1] = defaultPositions[idx-1] + advanceY; + + } + transform.transform(defaultPositions, 0, logicalPositions, 0, getNumGlyphs()+1); + + } + + /** + * Returnes the pixel bounds of this GlyphVector rendered at the + * specified x,y location with the given FontRenderContext. + * + * @param frc a FontRenderContext that is used + * @param x specified x coordinate value + * @param y specified y coordinate value + * @return a Rectangle that bounds pixels of this GlyphVector + */ + @Override + public Rectangle getPixelBounds(FontRenderContext frc, float x, float y) { + + double xM, yM, xm, ym; + + double minX = 0; + double minY = 0; + double maxX = 0; + double maxY = 0; + + for (int i = 0; i < this.getNumGlyphs(); i++) { + Rectangle glyphBounds = this.getGlyphPixelBounds(i, frc, 0, 0); + xm = glyphBounds.getMinX(); + ym = glyphBounds.getMinY(); + xM = glyphBounds.getMaxX(); + yM = glyphBounds.getMaxY(); + + if (i == 0) { + minX = xm; + minY = ym; + maxX = xM; + maxY = yM; + } + + if (minX > xm) { + minX = xm; + } + if (minY > ym) { + minY = ym; + } + if (maxX < xM) { + maxX = xM; + } + if (maxY < yM) { + maxY = yM; + } + } + return new Rectangle((int)(minX + x), (int)(minY + y), (int)(maxX - minX), (int)(maxY - minY)); + + } + + /** + * Returns the visual bounds of this GlyphVector. + * The visual bounds is the bounds of the total outline of + * this GlyphVector. + * @return a Rectangle2D that id the visual bounds of this GlyphVector + */ + @Override + public Rectangle2D getVisualBounds() { + float xM, yM, xm, ym; + float minX = 0; + float minY = 0; + float maxX = 0; + float maxY = 0; + boolean firstIteration = true; + + for (int i = 0; i < this.getNumGlyphs(); i++) { + Rectangle2D bounds = this.getGlyphVisualBounds(i).getBounds2D(); + if (bounds.getWidth() == 0){ + continue; + } + xm = (float)bounds.getX(); + ym = (float)bounds.getY(); + + xM = (float)(xm + bounds.getWidth()); + + yM = ym + (float) bounds.getHeight(); + + if (firstIteration) { + minX = xm; + minY = ym; + maxX = xM; + maxY = yM; + firstIteration = false; + } else { + if (minX > xm) { + minX = xm; + } + if (minY > ym) { + minY = ym; + } + if (maxX < xM) { + maxX = xM; + } + if (maxY < yM) { + maxY = yM; + } + + } + } + + return (this.getNumGlyphs() != 0) ? new Rectangle2D.Float(minX, minY, + (maxX - minX), (maxY - minY)) : null; + } + + /** + * Sets new position to the specified glyph. + */ + @Override + public void setGlyphPosition(int glyphIndex, Point2D newPos) { + if ((glyphIndex > vector.length) || (glyphIndex < 0)) { + // awt.43=glyphIndex is out of vector's limits + throw new IndexOutOfBoundsException(Messages.getString("awt.43")); //$NON-NLS-1$ + } + float x = (float)newPos.getX(); + float y = (float)newPos.getY(); + int index = glyphIndex << 1; + + if ((x != visualPositions[index]) || (y != visualPositions[index + 1])){ + visualPositions[index] = x; + visualPositions[index+1] = y; + layoutFlags = layoutFlags | FLAG_HAS_POSITION_ADJUSTMENTS; + } + + } + + /** + * Returns the position of the specified glyph relative to the origin of + * this GlyphVector + * @return a Point2D that the origin of the glyph with specified index + */ + @Override + public Point2D getGlyphPosition(int glyphIndex) { + if ((glyphIndex > vector.length) || (glyphIndex < 0)) { + // awt.43=glyphIndex is out of vector's limits + throw new IndexOutOfBoundsException(Messages.getString("awt.43")); //$NON-NLS-1$ + } + int index = glyphIndex << 1; + Point2D pos = new Point2D.Float(visualPositions[index], visualPositions[index+1]); + + // For last position we don't have to transform !! + if(glyphIndex==vector.length){ + return pos; + } + + AffineTransform at = getGlyphTransform(glyphIndex); + if ((at == null) || (at.isIdentity())){ + return pos; + } + + pos.setLocation(pos.getX() + at.getTranslateX(), pos.getY() + at.getTranslateY()); + + return pos; + } + + /** + * Sets new transform to the specified glyph. + * + * @param glyphIndex specified index of the glyph + * @param trans AffineTransform of the glyph with specified index + */ + @Override + public void setGlyphTransform(int glyphIndex, AffineTransform trans) { + if ((glyphIndex >= vector.length) || (glyphIndex < 0)) { + // awt.43=glyphIndex is out of vector's limits + throw new IndexOutOfBoundsException(Messages.getString("awt.43")); //$NON-NLS-1$ + } + + if ((trans == null) || (trans.isIdentity())) { + glsTransforms[glyphIndex] = null; + } else { + glsTransforms[glyphIndex] = new AffineTransform(trans); + layoutFlags = layoutFlags | FLAG_HAS_TRANSFORMS; + } + } + + /** + * Returns the affine transform of the specified glyph. + * + * @param glyphIndex specified index of the glyph + * @return an AffineTransform of the glyph with specified index + */ + @Override + public AffineTransform getGlyphTransform(int glyphIndex) { + if ((glyphIndex >= this.vector.length) || (glyphIndex < 0)) { + // awt.43=glyphIndex is out of vector's limits + throw new IndexOutOfBoundsException(Messages.getString("awt.43")); //$NON-NLS-1$ + } + return this.glsTransforms[glyphIndex]; + } + + /** + * Returns the metrics of the specified glyph. + * + * @param glyphIndex specified index of the glyph + */ + @Override + public GlyphMetrics getGlyphMetrics(int glyphIndex) { + + if ((glyphIndex < 0) || ((glyphIndex) >= this.getNumGlyphs())) { + // awt.43=glyphIndex is out of vector's limits + throw new IndexOutOfBoundsException(Messages.getString("awt.43")); //$NON-NLS-1$ + } + // TODO: is there a sence in GlyphMetrics + // if certain glyph or Font has a transform?? + return this.vector[glyphIndex].getGlyphMetrics(); + } + + /** + * Returns a justification information for the glyph with specified glyph + * index. + * @param glyphIndex index of a glyph which GlyphJustificationInfo is to be + * received + * @return a GlyphJustificationInfo object that contains glyph justification + * properties of the specified glyph + */ + @Override + public GlyphJustificationInfo getGlyphJustificationInfo(int glyphIndex) { + // TODO : Find out the source of Justification info + if (true) { + throw new RuntimeException("Method is not implemented"); //$NON-NLS-1$ + } + return null; + } + + /** + * Returns the FontRenderContext parameter of this GlyphVector. + */ + @Override + public FontRenderContext getFontRenderContext() { + return this.vectorFRC; + } + + /** + * Returns the visual bounds of the specified glyph. + * + * @param glyphIndex specified index of the glyph + */ + @Override + public Shape getGlyphVisualBounds(int glyphIndex) { + if ((glyphIndex < 0) || (glyphIndex >= this.getNumGlyphs())) { + // awt.43=glyphIndex is out of vector's limits + throw new IndexOutOfBoundsException(Messages.getString("awt.43")); //$NON-NLS-1$ + } + + int idx = glyphIndex << 1; + + AffineTransform fontTransform = this.transform; + double xOffs = fontTransform.getTranslateX(); + double yOffs = fontTransform.getTranslateY(); + + if (vector[glyphIndex].getWidth() == 0){ + return new Rectangle2D.Float((float)xOffs, (float)yOffs, 0, 0); + } + + AffineTransform at = AffineTransform.getTranslateInstance(xOffs, yOffs); + AffineTransform glyphTransform = getGlyphTransform(glyphIndex); + + if (transform.isIdentity() && ((glyphTransform == null) || glyphTransform.isIdentity())){ + Rectangle2D blackBox = vector[glyphIndex].getGlyphMetrics().getBounds2D(); + at.translate(visualPositions[idx], visualPositions[idx+1]); + return(at.createTransformedShape(blackBox)); + } + + GeneralPath shape = (GeneralPath)this.getGlyphOutline(glyphIndex); + shape.transform(at); + return shape.getBounds2D(); + } + + /** + * Returnes the pixel bounds of the specified glyph within GlyphVector + * rendered at the specified x,y location. + * + * @param glyphIndex index of the glyph + * @param frc a FontRenderContext that is used + * @param x specified x coordinate value + * @param y specified y coordinate value + * @return a Rectangle that bounds pixels of the specified glyph + */ + @Override + public Rectangle getGlyphPixelBounds(int glyphIndex, FontRenderContext frc, + float x, float y) { + // TODO : need to be implemented with FontRenderContext + if ((glyphIndex < 0) || (glyphIndex >= this.getNumGlyphs())) { + // awt.43=glyphIndex is out of vector's limits + throw new IndexOutOfBoundsException(Messages.getString("awt.43")); //$NON-NLS-1$ + } + + int idx = glyphIndex << 1; + + if (vector[glyphIndex].getWidth() == 0){ + AffineTransform fontTransform = this.transform; + double xOffs = x + visualPositions[idx] + fontTransform.getTranslateX(); + double yOffs = y + visualPositions[idx+1] + fontTransform.getTranslateY(); + return new Rectangle((int)xOffs, (int)yOffs, 0, 0); + } + + GeneralPath shape = (GeneralPath)this.getGlyphOutline(glyphIndex); + + AffineTransform at = AffineTransform.getTranslateInstance(x, y); + + if (frc != null){ + at.concatenate(frc.getTransform()); + } + + shape.transform(at); + + Rectangle bounds = shape.getBounds(); + return new Rectangle((int)bounds.getX(), (int)bounds.getY(), + (int)bounds.getWidth()-1, (int)bounds.getHeight()-1); + } + + /** + * Returns a Shape that encloses specified glyph. + * + * @param glyphIndex specified index of the glyph + */ + @Override + public Shape getGlyphOutline(int glyphIndex) { + if ((glyphIndex < 0) || (glyphIndex >= this.getNumGlyphs())) { + // awt.43=glyphIndex is out of vector's limits + throw new IndexOutOfBoundsException(Messages.getString("awt.43")); //$NON-NLS-1$ + } + + if (gvShapes[glyphIndex] == null) { + gvShapes[glyphIndex] = vector[glyphIndex].getShape(); + } + + GeneralPath gp = (GeneralPath)((GeneralPath)gvShapes[glyphIndex]).clone(); + + /* Applying GlyphVector font transform */ + AffineTransform at = (AffineTransform)this.transform.clone(); + + /* Applying Glyph transform */ + AffineTransform glyphAT = getGlyphTransform(glyphIndex); + if (glyphAT != null){ + at.preConcatenate(glyphAT); + } + + int idx = glyphIndex << 1; + + gp.transform(at); + gp.transform(AffineTransform.getTranslateInstance(visualPositions[idx], visualPositions[idx+1])); + return gp; + } + + + /** + * Returns a Shape that is the outline representation of this GlyphVector + * rendered at the specified x,y coordinates. + * + * @param x specified x coordinate value + * @param y specified y coordinate value + * @return a Shape object that is the outline of this GlyphVector + * at the specified coordinates. + */ + @Override + public Shape getOutline(float x, float y) { + GeneralPath gp = new GeneralPath(GeneralPath.WIND_EVEN_ODD); + for (int i = 0; i < this.vector.length; i++) { + GeneralPath outline = (GeneralPath)getGlyphOutline(i); + + /* Applying translation to actual visual bounds */ + outline.transform(AffineTransform.getTranslateInstance(x, y)); + gp.append(outline, false); + } + + return gp; + } + + /** + * Returns a Shape that is the outline representation of this GlyphVector. + * + * @return a Shape object that is the outline of this GlyphVector + */ + @Override + public Shape getOutline() { + return this.getOutline(0, 0); + } + + /** + * Returns an array of glyphcodes for the specified glyphs. + * + * @param beginGlyphIndex the start index + * @param numEntries the number of glyph codes to get + * @param codeReturn the array that receives glyph codes' values + * @return an array that receives glyph codes' values + */ + @Override + public int[] getGlyphCodes(int beginGlyphIndex, int numEntries, + int[] codeReturn) { + + if ((beginGlyphIndex < 0) || ((numEntries + beginGlyphIndex) > this.getNumGlyphs())) { + // awt.44=beginGlyphIndex is out of vector's range + throw new IndexOutOfBoundsException(Messages.getString("awt.44")); //$NON-NLS-1$ + } + + if (numEntries < 0) { + // awt.45=numEntries is out of vector's range + throw new IllegalArgumentException(Messages.getString("awt.45")); //$NON-NLS-1$ + } + + if (codeReturn == null) { + codeReturn = new int[numEntries]; + } + + for (int i = beginGlyphIndex; i < beginGlyphIndex + numEntries; i++) { + codeReturn[i-beginGlyphIndex] = this.vector[i].getGlyphCode(); + } + + return codeReturn; + } + + /** + * Returns an array of numEntries character indices for the specified glyphs. + * + * @param beginGlyphIndex the start index + * @param numEntries the number of glyph codes to get + * @param codeReturn the array that receives glyph codes' values + * @return an array that receives glyph char indices + */ + @Override + public int[] getGlyphCharIndices(int beginGlyphIndex, int numEntries, + int[] codeReturn) { + if ((beginGlyphIndex < 0) || (beginGlyphIndex >= this.getNumGlyphs())) { + // awt.44=beginGlyphIndex is out of vector's range + throw new IllegalArgumentException(Messages.getString("awt.44")); //$NON-NLS-1$ + } + + if ((numEntries < 0) + || ((numEntries + beginGlyphIndex) > this.getNumGlyphs())) { + // awt.45=numEntries is out of vector's range + throw new IllegalArgumentException(Messages.getString("awt.45")); //$NON-NLS-1$ + } + + if (codeReturn == null) { + codeReturn = new int[numEntries]; + } + + for (int i = 0; i < numEntries; i++) { + codeReturn[i] = this.getGlyphCharIndex(i + beginGlyphIndex); + } + return codeReturn; + } + + /** + * Returns an array of numEntries glyphs positions from beginGlyphIndex + * glyph in Glyph Vector. + * + * @param beginGlyphIndex the start index + * @param numEntries the number of glyph codes to get + * @param positionReturn the array that receives glyphs' positions + * @return an array of floats that receives glyph char indices + */ + @Override + public float[] getGlyphPositions(int beginGlyphIndex, int numEntries, + float[] positionReturn) { + + int len = (this.getNumGlyphs()+1) << 1; + beginGlyphIndex *= 2; + numEntries *= 2; + + if ((beginGlyphIndex < 0) || ((numEntries + beginGlyphIndex) > len)) { + // awt.44=beginGlyphIndex is out of vector's range + throw new IndexOutOfBoundsException(Messages.getString("awt.44")); //$NON-NLS-1$ + } + + if (numEntries < 0) { + // awt.45=numEntries is out of vector's range + throw new IllegalArgumentException(Messages.getString("awt.45")); //$NON-NLS-1$ + } + + if (positionReturn == null) { + positionReturn = new float[numEntries]; + } + + System.arraycopy(visualPositions, beginGlyphIndex, positionReturn, 0, numEntries); + + return positionReturn; + } + + /** + * Set numEntries elements of the visualPositions array from beginGlyphIndex + * of numEntries glyphs positions from beginGlyphIndex glyph in Glyph Vector. + * + * @param beginGlyphIndex the start index + * @param numEntries the number of glyph codes to get + * @param setPositions the array of positions to set + */ + public void setGlyphPositions(int beginGlyphIndex, int numEntries, + float[] setPositions) { + + int len = (this.getNumGlyphs()+1) << 1; + beginGlyphIndex *= 2; + numEntries *= 2; + + if ((beginGlyphIndex < 0) || ((numEntries + beginGlyphIndex) > len)) { + // awt.44=beginGlyphIndex is out of vector's range + throw new IndexOutOfBoundsException(Messages.getString("awt.44")); //$NON-NLS-1$ + } + + if (numEntries < 0) { + // awt.45=numEntries is out of vector's range + throw new IllegalArgumentException(Messages.getString("awt.45")); //$NON-NLS-1$ + } + + System.arraycopy(setPositions, 0, visualPositions, beginGlyphIndex, numEntries); + layoutFlags = layoutFlags & FLAG_HAS_POSITION_ADJUSTMENTS; + + } + + /** + * Set elements of the visualPositions array. + * + * @param setPositions the array of positions to set + */ + public void setGlyphPositions(float[] setPositions) { + + int len = (this.getNumGlyphs()+1) << 1; + if (len != setPositions.length){ + // awt.46=length of setPositions array differs from the length of positions array + throw new IllegalArgumentException(Messages.getString("awt.46")); //$NON-NLS-1$ + } + + System.arraycopy(setPositions, 0, visualPositions, 0, len); + layoutFlags = layoutFlags & FLAG_HAS_POSITION_ADJUSTMENTS; + + } + + + /** + * Returns glyph code of the specified glyph. + * + * @param glyphIndex specified index of the glyph + */ + @Override + public int getGlyphCode(int glyphIndex) { + if (glyphIndex >= this.vector.length || glyphIndex < 0) { + // awt.43=glyphIndex is out of vector's limits + throw new IndexOutOfBoundsException(Messages.getString("awt.43")); //$NON-NLS-1$ + } + return this.vector[glyphIndex].getGlyphCode(); + } + + /** + * Returns character index of the specified glyph. + * + * @param glyphIndex specified index of the glyph + */ + @Override + public int getGlyphCharIndex(int glyphIndex) { + + if ((glyphIndex < 0) || (glyphIndex >= this.getNumGlyphs())) { + // awt.43=glyphIndex is out of vector's limits + throw new IllegalArgumentException(Messages.getString("awt.43")); //$NON-NLS-1$ + } + + if ((this.layoutFlags & Font.LAYOUT_RIGHT_TO_LEFT) != 0) { + return this.charVector.length - glyphIndex - 1; + } + + return glyphIndex; + } + + /** + * Returns a character value of the specified glyph. + * + * @param glyphIndex specified index of the glyph + */ + public char getGlyphChar(int glyphIndex) { + + if ((glyphIndex < 0) || (glyphIndex >= this.getNumGlyphs())) { + // awt.43=glyphIndex is out of vector's limits + throw new IllegalArgumentException(Messages.getString("awt.43")); //$NON-NLS-1$ + } + return this.charVector[glyphIndex]; + } + + /** + * Assigns default positions to each glyph in this GlyphVector. + */ + @Override + public void performDefaultLayout() { + + System.arraycopy(logicalPositions, 0, visualPositions, 0, logicalPositions.length); + + // Set position changes flag to zero + clearLayoutFlags(GlyphVector.FLAG_HAS_POSITION_ADJUSTMENTS); + } + + /** + * Returns the number of glyphs in this Glyph Vector + */ + @Override + public int getNumGlyphs() { + return vector.length; + } + + /** + * Returns the logical bounds of this GlyphVector + */ + @Override + public Rectangle2D getLogicalBounds(){ + // XXX: for transforms where an angle between basis vectors is not 90 degrees + // Rectanlge2D class doesn't fit as Logical bounds. For this reason we use + // only non-transformed bounds!! + + float x = visualPositions[0]; + float width = visualPositions[visualPositions.length-2]; + + double scaleY = transform.getScaleY(); + + Rectangle2D bounds = new Rectangle2D.Float(x, (float)((-this.ascent-this.leading)*scaleY), width, (float)(this.height*scaleY)); + return bounds; + } + + + /** + * Checks whether given GlyphVector equals to this GlyphVector. + * @param glyphVector GlyphVector object to compare + */ + @Override + public boolean equals(GlyphVector glyphVector){ + if (glyphVector == this){ + return true; + } + + if (glyphVector != null) { + + if (!(glyphVector.getFontRenderContext().equals(this.vectorFRC) && + glyphVector.getFont().equals(this.font))){ + return false; + } + + try { + boolean eq = true; + for (int i = 0; i < getNumGlyphs(); i++) { + + int idx = i*2; + eq = (((CommonGlyphVector)glyphVector).visualPositions[idx] == this.visualPositions[idx]) && + (((CommonGlyphVector)glyphVector).visualPositions[idx+1] == this.visualPositions[idx+1]) && + (glyphVector.getGlyphCharIndex(i) == this.getGlyphCharIndex(i)); + + if (eq){ + AffineTransform trans = glyphVector.getGlyphTransform(i); + if (trans == null){ + eq = (this.glsTransforms[i] == null); + }else{ + eq = this.glsTransforms[i].equals(trans); + } + } + + if (!eq){ + return false; + } + } + + return eq; + } catch (ClassCastException e) { + } + } + + return false; + } + + + /** + * Returns flags describing the state of the GlyphVector. + */ + @Override + public int getLayoutFlags() { + return layoutFlags; + } + + /** + * Returns char with the specified index. + * + * @param index specified index of the char + * + */ + public char getChar(int index) { + return this.charVector[index]; + + } + + /** + * Clear desired flags in layout flags describing the state. + * + * @param clearFlags flags mask to clear + */ + + private void clearLayoutFlags(int clearFlags){ + layoutFlags &= ~clearFlags; + } + + /** + * Returns the logical bounds of the specified glyph within this CommonGlyphVector. + * + * @param glyphIndex index of the glyph to get it's logical bounds + * @return logical bounds of the specified glyph + */ + @Override + public Shape getGlyphLogicalBounds(int glyphIndex){ + if ((glyphIndex < 0) || (glyphIndex >= this.getNumGlyphs())){ + // awt.43=glyphIndex is out of vector's limits + throw new IndexOutOfBoundsException(Messages.getString("awt.43")); //$NON-NLS-1$ + } + Glyph glyph = this.vector[glyphIndex]; + + float x0 = visualPositions[glyphIndex*2]; + float y0 = visualPositions[glyphIndex*2+1]; + float advanceX = glyph.getGlyphPointMetrics().getAdvanceX(); + + GeneralPath gp = new GeneralPath(); + gp.moveTo(0, -ascent - leading); + gp.lineTo(advanceX ,-ascent - leading); + gp.lineTo(advanceX, descent); + gp.lineTo(0, descent); + gp.lineTo(0, -ascent - leading); + gp.closePath(); + + /* Applying GlyphVector font transform */ + AffineTransform at = (AffineTransform)this.transform.clone(); + + /* Applying Glyph transform */ + AffineTransform glyphTransform = getGlyphTransform(glyphIndex); + if (glyphTransform != null){ + at.concatenate(glyphTransform); + } + + /* Applying translation to actual visual bounds */ + at.preConcatenate(AffineTransform.getTranslateInstance(x0, y0)); + gp.transform(at); + return gp; + } + + /** + * Returns the Font parameter of this GlyphVector + */ + @Override + public Font getFont(){ + return this.font; + } + + +} \ No newline at end of file diff --git a/awt/org/apache/harmony/awt/gl/font/CompositeFont.java b/awt/org/apache/harmony/awt/gl/font/CompositeFont.java new file mode 100644 index 0000000..70cb334 --- /dev/null +++ b/awt/org/apache/harmony/awt/gl/font/CompositeFont.java @@ -0,0 +1,486 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Ilya S. Okomin + * @version $Revision$ + */ +package org.apache.harmony.awt.gl.font; + +import java.awt.font.FontRenderContext; +import java.awt.font.LineMetrics; +import java.awt.geom.AffineTransform; +import java.awt.geom.Rectangle2D; + +import org.apache.harmony.awt.gl.font.FontPeerImpl; +import org.apache.harmony.awt.gl.font.FontProperty; + +/** + * CompositeFont class is the implementation of logical font classes. + * Every logical font consists of several physical fonts that described + * in font.properties file according to the face name of this logical font. + */ +public class CompositeFont extends FontPeerImpl{ + + // a number of physical fonts that CompositeFont consist of + int numFonts; + + // font family name + String family; + + // font face name + String face; + + String[] fontNames; + + // an array of font properties applicable to this CompositeFont + FontProperty[] fontProperties; + + // an array of font peers applicable to this CompositeFont + public FontPeerImpl[] fPhysicalFonts; + + // missing glyph code field + int missingGlyphCode = -1; + + // line metrics of this font + LineMetricsImpl nlm = null; + + // cached num glyphs parameter of this font that is the sum of num glyphs of + // font peers composing this font + int cachedNumGlyphs = -1; + /** + * Creates CompositeFont object that is corresponding to the specified logical + * family name. + * + * @param familyName logical family name CompositeFont is to be created from + * @param faceName logical face name CompositeFont is to be created from + * @param _style style of the CompositeFont to be created + * @param _size size of the CompositeFont to be created + * @param fProperties an array of FontProperties describing physical fonts - + * parts of logical font + * @param physFonts an array of physical font peers related to the CompositeFont + * to be created + */ + public CompositeFont(String familyName, String faceName, int _style, int _size, FontProperty[] fProperties, FontPeerImpl[] physFonts){ + this.size = _size; + this.name = faceName; + this.family = familyName; + this.style = _style; + this.face = faceName; + this.psName = faceName; + this.fontProperties = fProperties;// !! Supposed that fProperties parameter != null + fPhysicalFonts = physFonts; + numFonts = fPhysicalFonts.length; + setDefaultLineMetrics("", null); //$NON-NLS-1$ + this.uniformLM = false; + } + + /** + * Returns the index of the FontPeer in array of physical fonts that is applicable + * for the given character. This font has to have the highest priority among fonts + * that can display this character and don't have exclusion range covering + * specified character. If there is no desired fonts -1 is returned. + * + * @param chr specified character + * @return index of the font from the array of physical fonts that will be used + * during processing of the specified character. + */ + public int getCharFontIndex(char chr){ + for (int i = 0; i < numFonts; i++){ + if (fontProperties[i].isCharExcluded(chr)){ + continue; + } + if (fPhysicalFonts[i].canDisplay(chr)){ + return i; + } + } + + return -1; + } + + /** + * Returns the index of the FontPeer in array of physical fonts that is applicable + * for the given character. This font has to have the highest priority among fonts + * that can display this character and don't have exclusion range covering + * specified character. If there is no desired fonts default value is returned. + * + * @param chr specified character + * @param defaultValue default index that is returned if the necessary font couldn't be found. + * @return index of the font from the array of physical fonts that will be used + * during processing of the specified character. + */ + public int getCharFontIndex(char chr, int defaultValue){ + for (int i = 0; i < numFonts; i++){ + if (fontProperties[i].isCharExcluded(chr)){ + continue; + } + if (fPhysicalFonts[i].canDisplay(chr)){ + return i; + } + } + + return defaultValue; + } + + /** + * Returns true if one of the physical fonts composing this font CompositeFont + * can display specified character. + * + * @param chr specified character + */ + @Override + public boolean canDisplay(char chr){ + return (getCharFontIndex(chr) != -1); + } + + /** + * Returns logical ascent (in pixels) + */ + @Override + public int getAscent(){ + return nlm.getLogicalAscent(); + } + + /** + * Returns LineMetrics instance scaled according to the specified transform. + * + * @param str specified String + * @param frc specified FontRenderContext + * @param at specified AffineTransform + */ + @Override + public LineMetrics getLineMetrics(String str, FontRenderContext frc , AffineTransform at){ + LineMetricsImpl lm = (LineMetricsImpl)(this.nlm.clone()); + lm.setNumChars(str.length()); + + if ((at != null) && (!at.isIdentity())){ + lm.scale((float)at.getScaleX(), (float)at.getScaleY()); + } + + return lm; + } + + /** + * Returns cached LineMetrics instance for the null string or creates it if + * it wasn't cached yet. + */ + @Override + public LineMetrics getLineMetrics(){ + if (nlm == null){ + setDefaultLineMetrics("", null); //$NON-NLS-1$ + } + + return this.nlm; + } + + /** + * Creates LineMetrics instance and set cached LineMetrics field to it. + * Created LineMetrics has maximum values of the idividual metrics of all + * composing physical fonts. If there is only one physical font - it's + * LineMetrics object is returned. + * + * @param str specified String + * @param frc specified FontRenderContext + */ + private void setDefaultLineMetrics(String str, FontRenderContext frc){ + LineMetrics lm = fPhysicalFonts[0].getLineMetrics(str, frc, null); + float maxCharWidth = (float)fPhysicalFonts[0].getMaxCharBounds(frc).getWidth(); + + if (numFonts == 1) { + this.nlm = (LineMetricsImpl)lm; + return; + } + + float[] baselineOffsets = lm.getBaselineOffsets(); + int numChars = str.length(); + + // XXX: default value - common for all Fonts + int baseLineIndex = lm.getBaselineIndex(); + + float maxUnderlineThickness = lm.getUnderlineThickness(); + float maxUnderlineOffset = lm.getUnderlineOffset(); + float maxStrikethroughThickness = lm.getStrikethroughThickness(); + float minStrikethroughOffset = lm.getStrikethroughOffset(); + float maxLeading = lm.getLeading(); // External leading + float maxHeight = lm.getHeight(); // Height of the font ( == (ascent + descent + leading)) + float maxAscent = lm.getAscent(); // Ascent of the font + float maxDescent = lm.getDescent(); // Descent of the font + + for (int i = 1; i < numFonts; i++){ + lm = fPhysicalFonts[i].getLineMetrics(str, frc, null); + if (maxUnderlineThickness < lm.getUnderlineThickness()){ + maxUnderlineThickness = lm.getUnderlineThickness(); + } + + if (maxUnderlineOffset < lm.getUnderlineOffset()){ + maxUnderlineOffset = lm.getUnderlineOffset(); + } + + if (maxStrikethroughThickness < lm.getStrikethroughThickness()){ + maxStrikethroughThickness = lm.getStrikethroughThickness(); + } + + if (minStrikethroughOffset > lm.getStrikethroughOffset()){ + minStrikethroughOffset = lm.getStrikethroughOffset(); + } + + if (maxLeading < lm.getLeading()){ + maxLeading = lm.getLeading(); + } + + if (maxAscent < lm.getAscent()){ + maxAscent = lm.getAscent(); + } + + if (maxDescent < lm.getDescent()){ + maxDescent = lm.getDescent(); + } + + float width = (float)fPhysicalFonts[i].getMaxCharBounds(frc).getWidth(); + if(maxCharWidth < width){ + maxCharWidth = width; + } + for (int j =0; j < baselineOffsets.length; j++){ + float[] offsets = lm.getBaselineOffsets(); + if (baselineOffsets[j] > offsets[j]){ + baselineOffsets[j] = offsets[j]; + } + } + + } + maxHeight = maxAscent + maxDescent + maxLeading; + + this.nlm = new LineMetricsImpl( + numChars, + baseLineIndex, + baselineOffsets, + maxUnderlineThickness, + maxUnderlineOffset, + maxStrikethroughThickness, + minStrikethroughOffset, + maxLeading, + maxHeight, + maxAscent, + maxDescent, + maxCharWidth); + + } + + /** + * Returns the number of glyphs in this CompositeFont object. + */ + @Override + public int getNumGlyphs(){ + if (this.cachedNumGlyphs == -1){ + + this.cachedNumGlyphs = 0; + + for (int i = 0; i < numFonts; i++){ + this.cachedNumGlyphs += fPhysicalFonts[i].getNumGlyphs(); + } + } + + return this.cachedNumGlyphs; + } + + /** + * Returns the italic angle of this object. + */ + @Override + public float getItalicAngle(){ + // !! only first physical font used to get this value + return fPhysicalFonts[0].getItalicAngle(); + } + + /** + * Returns rectangle that bounds the specified string in terms of composite line metrics. + * + * @param chars an array of chars + * @param start the initial offset in array of chars + * @param end the end offset in array of chars + * @param frc specified FontRenderContext + */ + public Rectangle2D getStringBounds(char[] chars, int start, int end, FontRenderContext frc){ + + LineMetrics lm = getLineMetrics(); + float minY = -lm.getAscent(); + float minX = 0; + float height = lm.getHeight(); + float width = 0; + + for (int i = start; i < end; i++){ + width += charWidth(chars[i]); + } + + Rectangle2D rect2D = new Rectangle2D.Float(minX, minY, width, height); + return rect2D; + + } + + /** + * Returns maximum rectangle that encloses all maximum char bounds of + * physical fonts composing this CompositeFont. + * + * @param frc specified FontRenderContext + */ + @Override + public Rectangle2D getMaxCharBounds(FontRenderContext frc){ + + Rectangle2D rect2D = fPhysicalFonts[0].getMaxCharBounds(frc); + float minY = (float)rect2D.getY(); + float maxWidth = (float)rect2D.getWidth(); + float maxHeight = (float)rect2D.getHeight(); + if (numFonts == 1){ + return rect2D; + } + + for (int i = 1; i < numFonts; i++){ + if (fPhysicalFonts[i] != null){ + rect2D = fPhysicalFonts[i].getMaxCharBounds(frc); + float y = (float)rect2D.getY(); + float mWidth = (float)rect2D.getWidth(); + float mHeight = (float)rect2D.getHeight(); + if (y < minY){ + minY = y; + } + if (mWidth > maxWidth){ + maxHeight = mWidth; + } + + if (mHeight > maxHeight){ + maxHeight = mHeight; + } + } + } + + rect2D = new Rectangle2D.Float(0, minY, maxWidth, maxHeight); + + return rect2D; + } + + /** + * Returns font name. + */ + @Override + public String getFontName(){ + return face; + } + + /** + * Returns font postscript name. + */ + @Override + public String getPSName(){ + return psName; + } + + /** + * Returns font family name. + */ + @Override + public String getFamily(){ + return family; + } + + /** + * Returns the code of the missing glyph. + */ + @Override + public int getMissingGlyphCode(){ + // !! only first physical font used to get this value + return fPhysicalFonts[0].getMissingGlyphCode(); + } + + /** + * Returns Glyph object corresponding to the specified character. + * + * @param ch specified char + */ + @Override + public Glyph getGlyph(char ch){ + for (int i = 0; i < numFonts; i++){ + if (fontProperties[i].isCharExcluded(ch)){ + continue; + } + + /* Control symbols considered to be supported by the font peer */ + if ((ch < 0x20) || fPhysicalFonts[i].canDisplay(ch)){ + return fPhysicalFonts[i].getGlyph(ch); + } + } + return getDefaultGlyph(); + } + + /** + * Returns width of the char with specified index. + * + * @param ind specified index of the character + */ + @Override + public int charWidth(int ind){ + return charWidth((char)ind); + } + + /** + * Returns width of the specified char. + * + * @param c specified character + */ + @Override + public int charWidth(char c){ + Glyph gl = this.getGlyph(c); + return (int)gl.getGlyphPointMetrics().getAdvanceX(); + } + + /** + * Returns debug information about this class. + */ + @Override + public String toString(){ + return new String(this.getClass().getName() + + "[name=" + this.name + //$NON-NLS-1$ + ",style="+ this.style + //$NON-NLS-1$ + ",fps=" + this.fontProperties + "]"); //$NON-NLS-1$ //$NON-NLS-2$ + } + + /** + * Returns Glyph object corresponding to the default glyph. + */ + @Override + public Glyph getDefaultGlyph(){ + // !! only first physical font used to get this value + return fPhysicalFonts[0].getDefaultGlyph(); + } + + /** + * Returns FontExtraMetrics object with extra metrics + * related to this CompositeFont. + */ + @Override + public FontExtraMetrics getExtraMetrics(){ + // Returns FontExtraMetrics instanse of the first physical + // Font from the array of fonts. + return fPhysicalFonts[0].getExtraMetrics(); + } + + /** + * Disposes CompositeFont object's resources. + */ + @Override + public void dispose() { + // Nothing to dispose + } +} diff --git a/awt/org/apache/harmony/awt/gl/font/FontExtraMetrics.java b/awt/org/apache/harmony/awt/gl/font/FontExtraMetrics.java new file mode 100644 index 0000000..047ba6d --- /dev/null +++ b/awt/org/apache/harmony/awt/gl/font/FontExtraMetrics.java @@ -0,0 +1,145 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Ilya S. Okomin + * @version $Revision$ + * + */ +package org.apache.harmony.awt.gl.font; + +/** + * Extra font metrics: sub/superscripts sizes, offsets, average char width. + */ +public class FontExtraMetrics { + + /* !! Subscript/superscript metrics are undefined for Type1. As a possible + * solution we can use values for Type1, that are proportionate to TrueType + * ones: + * SubscriptSizeX == 0.7 * fontSize + * SubscriptSizeY == 0.65 * fontSize + * SubscriptOffsetX == 0; + * SubscriptOffsetY == 0.15 * fontSize; + * SuperscriptSizeX == 0.7 * fontSize + * SuperscriptSizeY == 0.65 * fontSize + * SuperscriptOffsetX == 0; + * SuperscriptOffsetY == 0.45 * fontSize + * + */ + + /* + * The average width of characters in the font. + */ + private float lAverageCharWidth; + + /* + * Horizontal size for subscripts. + */ + private float lSubscriptSizeX; + + /* + * Vertical size for subscripts. + */ + private float lSubscriptSizeY; + + /* + * Horizontal offset for subscripts, the offset from the character origin + * to the origin of the subscript character. + */ + private float lSubscriptOffsetX; + + /* + * Vertical offset for subscripts, the offset from the character origin + * to the origin of the subscript character. + */ + private float lSubscriptOffsetY; + + /* + * Horizontal size for superscripts. + */ + private float lSuperscriptSizeX; + + /* + * Vertical size for superscripts. + */ + private float lSuperscriptSizeY; + + /* + * Horizontal offset for superscripts, the offset from the character + * base line to the base line of the superscript character. + */ + private float lSuperscriptOffsetX; + + /* + * Vertical offset for superscripts, the offset from the character + * base line to the base line of the superscript character. + */ + private float lSuperscriptOffsetY; + + public FontExtraMetrics(){ + // default constructor + } + + public FontExtraMetrics(float[] metrics){ + lAverageCharWidth = metrics[0]; + lSubscriptSizeX = metrics[1]; + lSubscriptSizeY = metrics[2]; + lSubscriptOffsetX = metrics[3]; + lSubscriptOffsetY = metrics[4]; + lSuperscriptSizeX = metrics[5]; + lSuperscriptSizeY = metrics[6]; + lSuperscriptOffsetX = metrics[7]; + lSuperscriptOffsetY = metrics[8]; + } + + public float getAverageCharWidth(){ + return lAverageCharWidth; + } + + public float getSubscriptSizeX(){ + return lSubscriptSizeX; + } + + public float getSubscriptSizeY(){ + return lSubscriptSizeY; + } + + public float getSubscriptOffsetX(){ + return lSubscriptOffsetX; + } + + public float getSubscriptOffsetY(){ + return lSubscriptOffsetY; + } + + public float getSuperscriptSizeX(){ + return lSuperscriptSizeX; + } + + public float getSuperscriptSizeY(){ + return lSuperscriptSizeY; + } + + public float getSuperscriptOffsetX(){ + return lSuperscriptOffsetX; + } + + public float getSuperscriptOffsetY(){ + return lSuperscriptOffsetY; + } + + +} diff --git a/awt/org/apache/harmony/awt/gl/font/FontFinder.java b/awt/org/apache/harmony/awt/gl/font/FontFinder.java new file mode 100644 index 0000000..09bcf5c --- /dev/null +++ b/awt/org/apache/harmony/awt/gl/font/FontFinder.java @@ -0,0 +1,121 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Oleg V. Khaschansky + * @version $Revision$ + * + * @date: Jul 12, 2005 + */ + +package org.apache.harmony.awt.gl.font; + +import java.awt.Font; +import java.awt.GraphicsEnvironment; +import java.util.List; +import java.util.Map; + +/** + * This class chooses the default font for the given text. + * If it finds the character which current font is unable to display + * it starts the next font run and looks for the font which is able to + * display the current character. It also caches the font mappings + * (index in the array containing all fonts) for the characters, + * using that fact that scripts are mainly contiguous in the UTF-16 encoding + * and there's a high probability that the upper byte will be the same for the + * next character as for the previous. This allows to save the space used for the cache. + */ +public class FontFinder { + private static final float DEFAULT_FONT_SIZE = 12; + + private static final Font fonts[] = + GraphicsEnvironment.getLocalGraphicsEnvironment().getAllFonts(); + + private static final int NUM_BLOCKS = 256; + private static final int BLOCK_SIZE = 256; + private static final int INDEX_MASK = 0xFF; + private static final int BLOCK_SHIFT = 8; + + // Maps characters into the fonts array + private static final int blocks[][] = new int[NUM_BLOCKS][]; + + /** + * Finds the font which is able to display the given character + * and saves the font mapping for this character + * @param c - character + * @return font + */ + static Font findFontForChar(char c) { + int blockNum = c >> BLOCK_SHIFT; + int index = c & INDEX_MASK; + + if (blocks[blockNum] == null) { + blocks[blockNum] = new int[BLOCK_SIZE]; + } + + if (blocks[blockNum][index] == 0) { + blocks[blockNum][index] = 1; + + for (int i=0; i runStarts, + Map fonts) { + Font prevFont = null; + Font currFont; + for (int i = runStart; i < runLimit; i++) { + currFont = findFontForChar(text[i]); + if (currFont != prevFont) { + prevFont = currFont; + Integer idx = new Integer(i); + fonts.put(idx, currFont); + if (i != runStart) { + runStarts.add(idx); + } + } + } + } +} diff --git a/awt/org/apache/harmony/awt/gl/font/FontManager.java b/awt/org/apache/harmony/awt/gl/font/FontManager.java new file mode 100644 index 0000000..8354e25 --- /dev/null +++ b/awt/org/apache/harmony/awt/gl/font/FontManager.java @@ -0,0 +1,819 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Ilya S. Okomin + * @version $Revision$ + */ +package org.apache.harmony.awt.gl.font; + +import java.awt.Font; +import java.awt.peer.FontPeer; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.SoftReference; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Locale; +import java.util.Properties; +import java.util.Vector; + +import org.apache.harmony.awt.gl.CommonGraphics2DFactory; +import org.apache.harmony.luni.util.NotImplementedException; + + +public abstract class FontManager { + + //???AWT + boolean NOT_IMP = false; + + /** + * array of font families names + */ + public String[] allFamilies; + + public static final String DEFAULT_NAME = "Default"; /* Default font name */ //$NON-NLS-1$ + public static final String DIALOG_NAME = "Dialog"; /* Dialog font name */ //$NON-NLS-1$ + + /** + * Set of constants applicable to the TrueType 'name' table. + */ + public static final byte FAMILY_NAME_ID = 1; /* Family name identifier */ + public static final byte FONT_NAME_ID = 4; /* Full font name identifier */ + public static final byte POSTSCRIPT_NAME_ID = 6; /* PostScript name identifier */ + public static final short ENGLISH_LANGID = 0x0409; /* English (United States)language identifier */ + + /** + * Set of constants describing font type. + */ + public static final byte FONT_TYPE_TT = 4; /* TrueType type (TRUETYPE_FONTTYPE) */ + public static final byte FONT_TYPE_T1 = 2; /* Type1 type (DEVICE_FONTTYPE) */ + public static final byte FONT_TYPE_UNDEF = 0; /* Undefined type */ + + // logical family types (indices in FontManager.LOGICAL_FONT_NAMES) + static final int DIALOG = 3; // FF_SWISS + static final int SANSSERIF = 1; // FF_SWISS + static final int DIALOGINPUT = 4; // FF_MODERN + static final int MONOSPACED = 2; // FF_MODERN + static final int SERIF = 0; // FF_ROMAN + + + /** + * FontProperty related constants. + */ + public static final String PLATFORM_FONT_NAME = "PlatformFontName"; //$NON-NLS-1$ + public static final String LOGICAL_FONT_NAME = "LogicalFontName"; //$NON-NLS-1$ + public static final String COMPONENT_INDEX = "ComponentIndex"; //$NON-NLS-1$ + public static final String STYLE_INDEX = "StyleIndex"; //$NON-NLS-1$ + + public static final String[] FONT_MAPPING_KEYS = { + "LogicalFontName.StyleName.ComponentIndex", "LogicalFontName.ComponentIndex" //$NON-NLS-1$ //$NON-NLS-2$ + }; + + public static final String FONT_CHARACTER_ENCODING = "fontcharset.LogicalFontName.ComponentIndex"; //$NON-NLS-1$ + + public static final String EXCLUSION_RANGES = "exclusion.LogicalFontName.ComponentIndex"; //$NON-NLS-1$ + + public static final String FONT_FILE_NAME = "filename.PlatformFontName"; //$NON-NLS-1$ + + /** + * Available logical font families names. + */ + public static final String[] LOGICAL_FONT_FAMILIES = { + "Serif", "SansSerif", "Monospaced", "Dialog", "DialogInput" //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ + }; + + /** + * Available logical font names. + */ + public static final String[] LOGICAL_FONT_NAMES = { + "serif", "serif.plain", "serif.bold", "serif.italic", "serif.bolditalic", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ + "sansserif", "sansserif.plain", "sansserif.bold", "sansserif.italic", "sansserif.bolditalic", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ + "monospaced", "monospaced.plain", "monospaced.bold", "monospaced.italic", "monospaced.bolditalic", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ + "dialog", "dialog.plain", "dialog.bold", "dialog.italic", "dialog.bolditalic", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ + "dialoginput", "dialoginput.plain", "dialoginput.bold", "dialoginput.italic", "dialoginput.bolditalic" //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ + }; + + /** + * Available logical font face names. + */ + public static final String[] LOGICAL_FONT_FACES = { + "Serif", "Serif.plain", "Serif.bold", "Serif.italic", "Serif.bolditalic", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ + "Sansserif", "Sansserif.plain", "Sansserif.bold", "Sansserif.italic", "Sansserif.bolditalic", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ + "Monospaced", "Monospaced.plain", "Monospaced.bold", "Monospaced.italic", "Monospaced.bolditalic", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ + "Dialog", "Dialog.plain", "Dialog.bold", "Dialog.italic", "Dialog.bolditalic", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ + "Dialoginput", "Dialoginput.plain", "Dialoginput.bold", "Dialoginput.italic", "Dialoginput.bolditalic" //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ + }; + + /** + * Set of font style names. + * Font.getStyle() corresponds to indexes in STYLE_NAMES array. + */ + public static final String[] STYLE_NAMES = { + "plain", "bold", "italic", "bolditalic" //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ + }; + + /** + * Logical font styles names table where font styles names used + * as the key and the value is the index of this style name. + */ + private static final Hashtable style_keys = new Hashtable(4); + + /** + * Initialize font styles keys table. + */ + static { + for (int i = 0; i < STYLE_NAMES.length; i++){ + style_keys.put(STYLE_NAMES[i], Integer.valueOf(i)); + } + } + + /** + * Return font style from the logical style name. + * + * @param lName style name of the logical face + */ + public static int getLogicalStyle(String lName){ + Integer value = style_keys.get(lName); + return value != null ? value.intValue(): -1; + } + + /** + * Set of possible "os" property values. + */ + public static final String[] OS_VALUES = { + "NT", "98", "2000", "Me", "XP", // For Windows //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ + "Redhat", "Turbo", "SuSE" // For Linux //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + }; + + /** + * Set of possible font.property file names. + * Language, Country, Encoding, OS, Version should be replaced with + * the values from current configuration. + */ + public static final String[] FP_FILE_NAMES = { + "/lib/font.properties.Language_Country_Encoding.OSVersion", //$NON-NLS-1$ + "/lib/font.properties.Language_Country_Encoding.OS", //$NON-NLS-1$ + "/lib/font.properties.Language_Country_Encoding.Version", //$NON-NLS-1$ + "/lib/font.properties.Language_Country_Encoding", //$NON-NLS-1$ + "/lib/font.properties.Language_Country.OSVersion", //$NON-NLS-1$ + "/lib/font.properties.Language_Country.OS", //$NON-NLS-1$ + "/lib/font.properties.Language_Country.Version", //$NON-NLS-1$ + "/lib/font.properties.Language_Country", //$NON-NLS-1$ + "/lib/font.properties.Language_Encoding.OSVersion", //$NON-NLS-1$ + "/lib/font.properties.Language_Encoding.OS", //$NON-NLS-1$ + "/lib/font.properties.Language_Encoding.Version", //$NON-NLS-1$ + "/lib/font.properties.Language_Encoding", //$NON-NLS-1$ + "/lib/font.properties.Language.OSVersion", //$NON-NLS-1$ + "/lib/font.properties.Language.OS", //$NON-NLS-1$ + "/lib/font.properties.Language.Version", //$NON-NLS-1$ + "/lib/font.properties.Language", //$NON-NLS-1$ + "/lib/font.properties.Encoding.OSVersion", //$NON-NLS-1$ + "/lib/font.properties.Encoding.OS", //$NON-NLS-1$ + "/lib/font.properties.Encoding.Version", //$NON-NLS-1$ + "/lib/font.properties.Encoding", //$NON-NLS-1$ + "/lib/font.properties.OSVersion", //$NON-NLS-1$ + "/lib/font.properties.OS", //$NON-NLS-1$ + "/lib/font.properties.Version", //$NON-NLS-1$ + "/lib/font.properties" //$NON-NLS-1$ + }; + + /** + * Table with all available font properties corresponding + * to the current system configuration. + */ + public Hashtable> fProperties = new Hashtable>(); + + public FontManager(){ + allFamilies = getAllFamilies(); + /* + * Creating and registering shutdown hook to free resources + * before object is destroyed. + */ + //???AWT + //DisposeNativeHook shutdownHook = new DisposeNativeHook(); + //Runtime.getRuntime().addShutdownHook(shutdownHook); + } + + /** + * Maximum number of unreferenced font peers to keep. + */ + public static final int EMPTY_FONTS_CAPACITY = 10; + + /** + * Locale - Language ID hash table. + */ + Hashtable tableLCID = new Hashtable(); + + /** + * Hash table that contains FontPeers instances. + */ + public Hashtable fontsTable = new Hashtable(); + + /** + * ReferenceQueue for HashMapReference objects to check + * if they were collected by garbage collector. + */ + public ReferenceQueue queue = new ReferenceQueue(); + + /** + * Singleton instance + */ + public final static FontManager inst = CommonGraphics2DFactory.inst.getFontManager(); + + /** + * Gets singleton instance of FontManager + * + * @return instance of FontManager implementation + */ + public static FontManager getInstance() { + return inst; + } + + /** + * Returns platform-dependent Font peer created from the specified + * Font object from the table with cached FontPeers instances. + * + * Note, this method checks whether FontPeer with specified parameters + * exists in the table with cached FontPeers' instances. If there is no needed + * instance - it is created and cached. + * + * @param fontName name of the font + * @param _fontStyle style of the font + * @param size font size + * + * @return platform dependent FontPeer implementation created from + * the specified parameters + */ + public FontPeer getFontPeer(String fontName, int _fontStyle, int size) { + updateFontsTable(); + + FontPeer peer = null; + String key; + String name; + int fontStyle = _fontStyle; + + int logicalIndex = getLogicalFaceIndex(fontName); + + if (logicalIndex != -1){ + name = getLogicalFaceFromFont(fontStyle, logicalIndex); + fontStyle = getStyleFromLogicalFace(name); + key = name.concat(String.valueOf(size)); + } else { + name = fontName; + key = name.concat(String.valueOf(fontStyle)). + concat(String.valueOf(size)); + } + + HashMapReference hmr = fontsTable.get(key); + if (hmr != null) { + peer = hmr.get(); + } + + if (peer == null) { + peer = createFontPeer(name, fontStyle, size, logicalIndex); + if (peer == null){ + peer = getFontPeer(DIALOG_NAME, fontStyle, size); + } + fontsTable.put(key, new HashMapReference(key, peer, queue)); + } + + return peer; + } + + /** + * Returns instance of font peer (logical or physical) according to the + * specified parameters. + * + * @param name font face name + * @param style style of the font + * @param size size of the font + * @param logicalIndex index of the logical face name in LOGICAL_FONT_FACES + * array or -1 if desired font peer is not logical. + */ + private FontPeer createFontPeer(String name, int style, int size, int logicalIndex){ + FontPeer peer; + if (logicalIndex != -1){ + peer = createLogicalFontPeer(name, style, size); + }else { + peer = createPhysicalFontPeer(name, style, size); + } + + return peer; + } + + /** + * Returns family name for logical face names as a parameter. + * + * @param faceName logical font face name + */ + public String getFamilyFromLogicalFace(String faceName){ + int pos = faceName.indexOf("."); //$NON-NLS-1$ + if (pos == -1){ + return faceName; + } + + return faceName.substring(0, pos); + } + + /** + * Returns new logical font peer for the parameters specified using font + * properties. + * + * @param faceName face name of the logical font + * @param style style of the font + * @param size font size + * + */ + private FontPeer createLogicalFontPeer(String faceName, int style, int size){ + String family = getFamilyFromLogicalFace(faceName); + FontProperty[] fps = getFontProperties(family.toLowerCase() + "." + style); //$NON-NLS-1$ + if (fps != null){ + int numFonts = fps.length; + FontPeerImpl[] physicalFonts = new FontPeerImpl[numFonts]; + for (int i = 0; i < numFonts; i++){ + FontProperty fp = fps[i]; + + String name = fp.getName(); + int fpStyle = fp.getStyle(); + String key = name.concat(String.valueOf(fpStyle)). + concat(String.valueOf(size)); + + HashMapReference hmr = fontsTable.get(key); + if (hmr != null) { + physicalFonts[i] = (FontPeerImpl)hmr.get(); + } + + if (physicalFonts[i] == null){ + physicalFonts[i] = (FontPeerImpl)createPhysicalFontPeer(name, fpStyle, size); + fontsTable.put(key, new HashMapReference(key, physicalFonts[i], queue)); + } + + if (physicalFonts[i] == null){ + physicalFonts[i] = (FontPeerImpl)getDefaultFont(style, size); + } + } + return new CompositeFont(family, faceName, style, size, fps, physicalFonts); + } + + // if there is no property for this logical font - default font is to be + // created + FontPeerImpl peer = (FontPeerImpl)getDefaultFont(style, size); + + return peer; + } + + /** + * Returns new physical font peer for the parameters specified using font properties + * This method must be overridden by subclasses implementations. + * + * @param faceName face name or family name of the font + * @param style style of the font + * @param size font size + * + */ + public abstract FontPeer createPhysicalFontPeer(String name, int style, int size); + + /** + * Returns default font peer class with "Default" name that is usually + * used when font with specified font names and style doesn't exsist + * on a system. + * + * @param style style of the font + * @param size size of the font + */ + public FontPeer getDefaultFont(int style, int size){ + updateFontsTable(); + + FontPeer peer = null; + String key = DEFAULT_NAME.concat(String.valueOf(style)). + concat(String.valueOf(size)); + + HashMapReference hmr = fontsTable.get(key); + if (hmr != null) { + peer = hmr.get(); + } + + if (peer == null) { + peer = createDefaultFont(style, size); + + ((FontPeerImpl)peer).setFamily(DEFAULT_NAME); + ((FontPeerImpl)peer).setPSName(DEFAULT_NAME); + ((FontPeerImpl)peer).setFontName(DEFAULT_NAME); + + fontsTable.put(key, new HashMapReference(key, peer, queue)); + } + + return peer; + } + + /** + * + * Returns new default font peer with "Default" name for the parameters + * specified. This method must be overridden by subclasses implementations. + * + * @param style style of the font + * @param size size of the font + */ + public abstract FontPeer createDefaultFont(int style, int size); + + /** + * Returns face name of the logical font, which is the result + * of specified font style and face style union. + * + * @param fontStyle specified style of the font + * @param logicalIndex index of the specified face from the + * LOGICAL_FONT_FACES array + * @return resulting face name + */ + public String getLogicalFaceFromFont(int fontStyle, int logicalIndex){ + int style = 0; + String name = LOGICAL_FONT_FACES[logicalIndex]; + int pos = name.indexOf("."); //$NON-NLS-1$ + + if (pos == -1){ + return createLogicalFace(name, fontStyle); + } + + String styleName = name.substring(pos+1); + name = name.substring(0, pos); + + // appending font style to the face style + style = fontStyle | getLogicalStyle(styleName); + + return createLogicalFace(name, style); + } + + /** + * Function returns style value from logical face name. + * + * @param name face name + * @return font style + */ + public int getStyleFromLogicalFace(String name){ + int style; + int pos = name.indexOf("."); //$NON-NLS-1$ + + if (pos == -1){ + return Font.PLAIN; + } + + String styleName = name.substring(pos+1); + + style = getLogicalStyle(styleName); + + return style; + } + + /** + * Returns logical face name corresponding to the logical + * family name and style of the font. + * + * @param family font family + * @param styleIndex index of the style name from the STYLE_NAMES array + */ + public String createLogicalFace(String family, int styleIndex){ + return family + "." + STYLE_NAMES[styleIndex]; //$NON-NLS-1$ + } + + /** + * Return language Id from LCID hash corresponding to the specified locale + * + * @param l specified locale + */ + public Short getLCID(Locale l){ + if (this.tableLCID.size() == 0){ + initLCIDTable(); + } + + return tableLCID.get(l.toString()); + } + + /** + * Platform-dependent LCID table init. + */ + public abstract void initLCIDTable(); + + /** + * Freeing native resources. This hook is used to avoid + * sudden application exit and to free resources created in native code. + */ + private class DisposeNativeHook extends Thread { + + @Override + public void run() { + try{ + /* Disposing native font peer's resources */ + Enumeration kEnum = fontsTable.keys(); + + while(kEnum.hasMoreElements()){ + Object key = kEnum.nextElement(); + HashMapReference hmr = fontsTable.remove(key); + FontPeerImpl delPeer = (FontPeerImpl)hmr.get(); + + if ((delPeer != null) && (delPeer.getClass() != CompositeFont.class)){ + // there's nothing to dispose in CompositeFont objects + delPeer.dispose(); + } + } + } catch (Throwable t){ + throw new RuntimeException(t); + } + } + } + + /** + * Returns File object, created in a directory + * according to the System, where JVM is being ran. + * + * In Linux case we use ".fonts" directory (for fontconfig purpose), + * where font file from the stream will be stored, hence in LinuxFontManager this + * method is overridden. + * In Windows case we use Windows temp directory (default implementation) + * + */ + public File getTempFontFile()throws IOException{ + //???AWT + /* + File fontFile = File.createTempFile("jFont", ".ttf"); //$NON-NLS-1$ //$NON-NLS-2$ + fontFile.deleteOnExit(); + + return fontFile; + */ + if(NOT_IMP) + throw new NotImplementedException("getTempFontFile not Implemented"); + return null; + } + + /** + * Returns File object with font properties. It's name obtained using current + * system configuration properties and locale settings. If no appropriate + * file is found method returns null. + */ + public static File getFontPropertyFile(){ + File file = null; + + String javaHome = System.getProperty("java.home"); //$NON-NLS-1$ + Locale l = Locale.getDefault(); + String language = l.getLanguage(); + String country = l.getCountry(); + String fileEncoding = System.getProperty("file.encoding"); //$NON-NLS-1$ + + String os = System.getProperty("os.name"); //$NON-NLS-1$ + + int i = 0; + + // OS names from system properties don't match + // OS identifiers used in font.property files + for (; i < OS_VALUES.length; i++){ + if (os.endsWith(OS_VALUES[i])){ + os = OS_VALUES[i]; + break; + } + } + + if (i == OS_VALUES.length){ + os = null; + } + + String version = System.getProperty("os.version"); //$NON-NLS-1$ + String pathname; + + for (i = 0; i < FP_FILE_NAMES.length; i++){ + pathname = FP_FILE_NAMES[i]; + if (os != null){ + pathname = pathname.replaceFirst("OS", os); //$NON-NLS-1$ + } + + pathname = javaHome + pathname; + + pathname = pathname.replaceAll("Language", language). //$NON-NLS-1$ + replaceAll("Country", country). //$NON-NLS-1$ + replaceAll("Encoding", fileEncoding). //$NON-NLS-1$ + replaceAll("Version", version); //$NON-NLS-1$ + + file = new File(pathname); + + if (file.exists()){ + break; + } + } + + return file.exists() ? file : null; + } + + /** + * Returns an array of integer range values + * if the parameter exclusionString has format: + * Range + * Range [, exclusionString] + * + * Range: + * Char-Char + * + * Char: + * HexDigit HexDigit HexDigit HexDigit + * + * Method returns null if the specified string is null. + * + * @param exclusionString string parameter in specified format + */ + public static int[] parseIntervals(String exclusionString){ + int[] results = null; + + if (exclusionString == null){ + return null; + } + + String[] intervals = exclusionString.split(","); //$NON-NLS-1$ + + if (intervals != null){ + int num = intervals.length; + if (num > 0){ + results = new int[intervals.length << 1]; + for (int i = 0; i < intervals.length; i++){ + String ranges[] = intervals[i].split("-"); //$NON-NLS-1$ + results[i*2] = Integer.parseInt(ranges[0], 16); + results[i*2+1] = Integer.parseInt(ranges[1], 16); + + } + } + } + return results; + } + + /** + * Returns Properties from the properties file or null if + * there is an error with FileInputStream processing. + * + * @param file File object containing properties + */ + public static Properties getProperties(File file){ + Properties props = null; + FileInputStream fis = null; + try{ + fis = new FileInputStream(file); + props = new Properties(); + props.load(fis); + } catch (Exception e){ + System.out.println(e); + } + return props; + } + + /** + * Returns an array of FontProperties from the properties file + * with the specified property name "logical face.style". E.g. + * "dialog.2" corresponds to the font family Dialog with bold style. + * + * @param fpName key of the font properties in the properties set + */ + public FontProperty[] getFontProperties(String fpName){ + Vector props = fProperties.get(fpName); + + if (props == null){ + return null; + } + + int size = props.size(); + + if (size == 0){ + return null; + } + + FontProperty[] fps = new FontProperty[size]; + for (int i=0; i < fps.length; i++){ + fps[i] = props.elementAt(i); + } + return fps; + } + + /** + * Returns index of the font name in array of font names or -1 if + * this font is not logical. + * + * @param fontName specified font name + */ + public static int getLogicalFaceIndex(String fontName){ + for (int i=0; i { + + /** + * The key for Hashtable. + */ + private final String key; + + /** + * Creates a new soft reference with the key specified and + * adding this reference in the reference queue specified. + * + * @param key the key in Hashtable + * @param value object that corresponds to the key + * @param queue reference queue where reference is to be added + */ + public HashMapReference(final String key, final FontPeer value, + final ReferenceQueue queue) { + super(value, queue); + this.key = key; + } + + /** + * Returns the key that corresponds to the SoftReference instance + * + * @return the key in Hashtable with cached references + */ + public Object getKey() { + return key; + } + } + + /** + * Removes keys from the Hashtable with font peers which corresponding + * HashMapReference objects were garbage collected. + */ + private void updateFontsTable() { + HashMapReference r; + //???AWT + //while ((r = (HashMapReference)queue.poll()) != null) { + // fontsTable.remove(r.getKey()); + //} + } + +} + + diff --git a/awt/org/apache/harmony/awt/gl/font/FontMetricsImpl.java b/awt/org/apache/harmony/awt/gl/font/FontMetricsImpl.java new file mode 100644 index 0000000..7783317 --- /dev/null +++ b/awt/org/apache/harmony/awt/gl/font/FontMetricsImpl.java @@ -0,0 +1,282 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Ilya S. Okomin + * @version $Revision$ + */ +package org.apache.harmony.awt.gl.font; + +import com.android.internal.awt.AndroidGraphics2D; + +import java.awt.Font; +import java.awt.FontMetrics; +//import java.awt.Paint; +import java.awt.geom.AffineTransform; + +import android.graphics.Paint; + +/** + * FontMetrics implementation + */ + +public class FontMetricsImpl extends FontMetrics { + + private static final long serialVersionUID = 844695615201925138L; + + // ascent of the font + private int ascent; + + // descent of the font + private int descent; + + // leading of the font + private int leading; + + // maximum ascent of the font + private int maxAscent; + + // maximum descent of the font + private int maxDescent; + + // maximum advance of the font + private int maxAdvance; + + // array of char advance widths + private int[] widths = new int[256]; + + // font peer corresponding to this FontPeerImpl + private transient FontPeerImpl peer; + + // X scale parameter of the font transform + private float scaleX = 1; + + public AndroidGraphics2D mSg; + + private Font mFn; + + // Y scale parameter of the font transform + private float scaleY = 1; + + /** + * Creates new FontMericsImpl object described by the specified Font. + * + * @param fnt + * the specified Font object + */ + public FontMetricsImpl(Font fnt) { + super(fnt); + this.mFn = fnt; + + mSg = AndroidGraphics2D.getInstance(); + Paint p = mSg.getAndroidPaint(); + + this.ascent = (int)-p.ascent(); + this.descent = (int)p.descent(); + this.leading = p.getFontMetricsInt().leading; + + AffineTransform at = fnt.getTransform(); + if (!at.isIdentity()) { + scaleX = (float) at.getScaleX(); + scaleY = (float) at.getScaleY(); + } + + /* + * metrics[5] - strikethrough thickness

+ * -metrics[6] - strikethrough offset

+ * metrics[7] - maximum char width

+ * metrics[8] - ascent in pixels

+ * metrics[9] - descent in pixles

+ * metrics[10] - external leading in pixels

+ * metrics[11] - underline thickness in pixels

+ * -metrics[12] - underline offset in pixels

+ * metrics[13] - strikethrough thickness in pixels

+ * -metrics[14] - strikethrough offset in pixels

+ * metrics[15] - maximum char width in pixels

+ + * @param _baselineData an array of 3 elements with baseline offsets metrics

+ * _baselineData[0] - roman baseline offset

+ * _baselineData[1] - center baseline offset

+ * _baselineData[2] - hanging baseline offset

+ */ + } + + + /** + * Initialize the array of the first 256 chars' advance widths of the Font + * describing this FontMetricsImpl object. + */ + private void initWidths() { + + this.widths = new int[256]; + for (int chr = 0; chr < 256; chr++) { + widths[chr] = (int) (getFontPeer().charWidth((char) chr) * scaleX); + } + + } + + /** + * Returns the ascent of the Font describing this FontMetricsImpl object. + */ + @Override + public int getAscent() { + return this.ascent; + } + + /** + * Returns the descent of the Font describing this FontMetricsImpl object. + */ + @Override + public int getDescent() { + return this.descent; + } + + /** + * Returns the leading of the Font describing this FontMetricsImpl object. + */ + @Override + public int getLeading() { + return this.leading; + } + + /** + * Returns the advance width of the specified char of the Font describing + * this FontMetricsImpl object. + * + * @param ch + * the char which width is to be returned + * @return the advance width of the specified char of the Font describing + * this FontMetricsImpl object + */ + @Override + public int charWidth(int ch) { + if (ch < 256) { + return widths[ch]; + } + + return getFontPeer().charWidth((char) ch); + } + + /** + * Returns the advance width of the specified char of the Font describing + * this FontMetricsImpl object. + * + * @param ch + * the char which width is to be returned + * @return the advance width of the specified char of the Font describing + * this FontMetricsImpl object + */ + @Override + public int charWidth(char ch) { + if (ch < 256) { + return widths[ch]; + } + return (int) (getFontPeer().charWidth(ch) * scaleX); + } + + /** + * Returns the maximum advance of the Font describing this FontMetricsImpl + * object. + */ + @Override + public int getMaxAdvance() { + return this.maxAdvance; + } + + /** + * Returns the maximum ascent of the Font describing this FontMetricsImpl + * object. + */ + @Override + public int getMaxAscent() { + return this.maxAscent; + } + + /** + * Returns the maximum descent of the Font describing this FontMetricsImpl + * object. + */ + @SuppressWarnings("deprecation") + @Deprecated + @Override + public int getMaxDecent() { + return this.maxDescent; + } + + /** + * Returns the maximum descent of the Font describing this FontMetricsImpl + * object. + */ + @Override + public int getMaxDescent() { + return this.maxDescent; + } + + /** + * Returns the advance widths of the first 256 characters in the Font + * describing this FontMetricsImpl object. + */ + @Override + public int[] getWidths() { + return this.widths; + } + + /** + * Returns the total advance width of the specified string in the metrics of + * the Font describing this FontMetricsImpl object. + * + * @param str + * the String which width is to be measured + * @return the total advance width of the specified string in the metrics of + * the Font describing this FontMetricsImpl object + */ + @Override + public int stringWidth(String str) { + + int width = 0; + char chr; + + for (int i = 0; i < str.length(); i++) { + chr = str.charAt(i); + width += charWidth(chr); + } + return width; + + /* + * float res = 0; int ln = str.length(); char[] c = new char[ln]; float[] f = + * new float[ln]; str.getChars(0, ln, c, 0); mSg.getPaint().getTextWidths(c, 0, + * ln, f); + * + * for(int i = 0; i < f.length; i++) { res += f[i]; } return (int)res; + */ + } + + /** + * Returns FontPeer implementation of the Font describing this + * FontMetricsImpl object. + * + * @return a FontPeer object, that is the platform dependent FontPeer + * implementation for the Font describing this FontMetricsImpl + * object. + */ + @SuppressWarnings("deprecation") + public FontPeerImpl getFontPeer() { + if (peer == null) { + peer = (FontPeerImpl) font.getPeer(); + } + return peer; + } +} diff --git a/awt/org/apache/harmony/awt/gl/font/FontPeerImpl.java b/awt/org/apache/harmony/awt/gl/font/FontPeerImpl.java new file mode 100644 index 0000000..14ff997 --- /dev/null +++ b/awt/org/apache/harmony/awt/gl/font/FontPeerImpl.java @@ -0,0 +1,499 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Ilya S. Okomin + * @version $Revision$ + */ +package org.apache.harmony.awt.gl.font; + + +import com.android.internal.awt.AndroidGraphics2D; +import com.android.internal.awt.AndroidGraphicsFactory; + +import java.awt.Graphics2D; +import java.awt.Toolkit; +import java.awt.geom.AffineTransform; +import java.awt.geom.Rectangle2D; +import java.awt.peer.FontPeer; + +import java.awt.font.FontRenderContext; +import java.awt.font.LineMetrics; +import java.util.ArrayList; +import java.util.Locale; + +import org.apache.harmony.awt.internal.nls.Messages; + +import android.graphics.Paint; + +/** + * Abstract class for platform dependent peer implementation of the Font class. + */ +public abstract class FontPeerImpl implements FontPeer{ + + // ascent of this font peer (in pixels) + int ascent; + + // descent of this font peer (in pixels) + int descent; + + // leading of this font peer (in pixels) + int leading; + + // logical maximum advance of this font peer (in pixels) + int maxAdvance; + + // the height of this font peer + float height; + + // the style of this font peer + int style; + + // the point size of this font peer (in pixels) + int size; + + // the logical hight of this font peer (in pixels) + int logicalHeight; + + // the name of this font peer + String name; + + // family name of this font peer + String fontFamilyName; + + // the Face name of this font peer + String faceName; + + // bounds rectanlge of the largest character in this font peer + Rectangle2D maxCharBounds; + + // italic angle value of this font peer + float italicAngle = 0.0f; + + // the number of glyphs supported by this font peer + int numGlyphs = 0; + + // native font handle + long pFont; + + // cached line metrics object + LineMetricsImpl nlm; + + // the postscript name of this font peer + String psName = null; + + /** + * Default glyph index, that is used, when the desired glyph + * is unsupported in this Font. + */ + public char defaultChar = (char)0xFFFF; + + /** + * Uniform LineMetrics flag, that is false for CompositeFont. + * Default value is true. + */ + boolean uniformLM = true; + + /** + * Flag of the type of this Font that is indicate is the Font + * has TrueType or Type1 type. Default value is FONT_TYPE_UNDEF. + */ + int fontType = FontManager.FONT_TYPE_UNDEF; + + /** + * Flag if this Font was created from stream, + * this parameter used in finilize method. + */ + private boolean createdFromStream = false; + + // temorary Font file name, if this FontPeerImpl was created from InputStream + private String tempFontFileName = null; + + // cached FontExtraMetrics object related to this font peer + FontExtraMetrics extraMetrix = null; + + public abstract FontExtraMetrics getExtraMetrics(); + + /** + * Returns LineMetrics object with specified parameters + * @param str specified String + * @param frc specified render context + * @param at specified affine transform + * @return + */ + public abstract LineMetrics getLineMetrics(String str, FontRenderContext frc, AffineTransform at); + + /** + * Returns postscript name of the font. + */ + public abstract String getPSName(); + + //private Graphics2D g = ((AndroidGraphicsFactory)Toolkit.getDefaultToolkit().getGraphicsFactory()).getGraphics2D(); + //private Graphics2D g = AndroidGraphics2D.getInstance(); + + /** + * Set postscript name of the font to the specified parameter. + */ + public void setPSName(String name){ + this.psName = name; + } + + /** + * Returns code of the missing glyph. + */ + public abstract int getMissingGlyphCode(); + + /** + * Returns Glyph representation of the given char. + * @param ch specified char + */ + public abstract Glyph getGlyph(char ch); + + /** + * Disposes nesessary resources. + */ + public abstract void dispose(); + + /** + * Returns Glyph represeting missing char. + */ + public abstract Glyph getDefaultGlyph(); + + /** + * Returns true if this FontPeerImpl can display the specified char + */ + public abstract boolean canDisplay(char c); + + /** + * Returns family name of the font in specified locale settings. + * @param l specified Locale + */ + public String getFamily(Locale l){ + return this.getFamily(); + } + + /** + * Sets family name of the font in specified locale settings. + */ + public void setFamily(String familyName){ + this.fontFamilyName = familyName; + } + + /** + * Returns face name of the font in specified locale settings. + * @param l specified Locale + */ + public String getFontName(Locale l){ + return this.getFontName(); + } + + /** + * Sets font name of the font in specified locale settings. + */ + public void setFontName(String fontName){ + this.faceName = fontName; + } + + /** + * Returns true, if this font peer was created from InputStream, false otherwise. + * In case of creating fonts from InputStream some font peer implementations + * may need to free temporary resources. + */ + public boolean isCreatedFromStream(){ + return this.createdFromStream; + } + + /** + * Sets createdFromStream flag to the specified parameter. + * If parameter is true it means font peer was created from InputStream. + * + * @param value true, if font peer was created from InputStream + */ + public void setCreatedFromStream(boolean value){ + this.createdFromStream = value; + } + + /** + * Returns font file name of this font. + */ + public String getTempFontFileName(){ + return this.tempFontFileName; + } + + /** + * Sets font file name of this font to the specified one. + * @param value String representing font file name + */ + public void setFontFileName(String value){ + this.tempFontFileName = value; + } + + /** + * Returns the advance width of the specified char of this FontPeerImpl. + * Note, if glyph is absent in the font's glyphset - returned value + * is the advance of the deafualt glyph. For escape-chars returned + * width value is 0. + * + * @param ch the char which width is to be returned + * @return the advance width of the specified char of this FontPeerImpl + */ + public int charWidth(char ch) { + Paint p; + AndroidGraphics2D g = AndroidGraphics2D.getInstance(); + if(g == null) { + throw new RuntimeException("AndroidGraphics2D not instantiated!"); + } + p = ((AndroidGraphics2D)g).getAndroidPaint(); + char[] ca = {ch}; + float[] fa = new float[1]; + p.getTextWidths(ca, 0, 1, fa); + return (int)fa[0]; + } + + /** + * Returns the advance width of the specified char of this FontPeerImpl. + * + * @param ind the char which width is to be returned + * @return the advance width of the specified char of this FontPeerImpl + */ + public int charWidth(int ind) { + return charWidth((char)ind); + } + + /** + * Returns an array of Glyphs that represent characters from the specified + * Unicode range. + * + * @param uFirst start position in Unicode range + * @param uLast end position in Unicode range + * @return + */ + public Glyph[] getGlyphs(char uFirst, char uLast) { + + char i = uFirst; + int len = uLast - uFirst; + ArrayList lst = new ArrayList(len); + + if (size < 0) { + // awt.09=min range bound value is greater than max range bound + throw new IllegalArgumentException(Messages.getString("awt.09")); //$NON-NLS-1$ + } + + while (i < uLast) { + lst.add(this.getGlyph(i)); + } + + return (Glyph[]) lst.toArray(); + } + + /** + * Returns an array of Glyphs representing given array of chars. + * + * @param chars specified array of chars + */ + public Glyph[] getGlyphs(char[] chars) { + if (chars == null){ + return null; + } + + Glyph[] result = new Glyph[chars.length]; + + for (int i = 0; i < chars.length; i++) { + result[i] = this.getGlyph(chars[i]); + } + return result; + } + + /** + * Returns an array of Glyphs representing given string. + * + * @param str specified string + */ + public Glyph[] getGlyphs(String str) { + char[] chars = str.toCharArray(); + return this.getGlyphs(chars); + } + + /** + * Returns family name of this FontPeerImpl. + */ + public String getFamily() { + return fontFamilyName; + } + + /** + * Returns face name of this FontPeerImpl. + */ + public String getFontName() { + if (this.fontType == FontManager.FONT_TYPE_T1){ + return this.fontFamilyName; + } + + return faceName; + } + + /** + * Returns height of this font peer in pixels. + */ + public int getLogicalHeight() { + return logicalHeight; + } + + /** + * Sets height of this font peer in pixels to the given value. + * + * @param newHeight new height in pixels value + */ + public void setLogicalHeight(int newHeight) { + logicalHeight = newHeight; + } + + /** + * Returns font size. + */ + public int getSize() { + return size; + } + + /** + * Returns font style. + */ + public int getStyle() { + return style; + } + + /** + * Returns font name. + */ + public String getName() { + return name; + } + + /** + * Returns the bounds of the largest char in this FontPeerImpl in + * specified render context. + * + * @param frc specified FontRenderContext + */ + public Rectangle2D getMaxCharBounds(FontRenderContext frc) { + return maxCharBounds; + } + + /** + * Returns the number of glyphs in this FontPeerImpl. + */ + public int getNumGlyphs() { + return numGlyphs; + } + + /** + * Returns tangens of the italic angle of this FontPeerImpl. + * If the FontPeerImpl has TrueType font type, italic angle value can be + * calculated as (CharSlopeRun / CharSlopeRise) in terms of GDI. + */ + public float getItalicAngle() { + return italicAngle; + } + + /** + * Returns height of this font peer. + */ + public float getHeight(){ + return height; + } + + /** + * Returns cached LineMetrics object of this font peer. + */ + public LineMetrics getLineMetrics(){ + return nlm; + } + + /** + * Returns native font handle of this font peer. + */ + public long getFontHandle(){ + return pFont; + } + + /** + * Returns ascent of this font peer. + */ + public int getAscent(){ + Paint p; + AndroidGraphics2D g = AndroidGraphics2D.getInstance(); + if(g == null) { + throw new RuntimeException("AndroidGraphics2D not instantiated!"); + } + p = ((AndroidGraphics2D)g).getAndroidPaint(); + return (int)p.ascent(); + //return ascent; + } + + /** + * Returns descent of this font peer. + */ + public int getDescent(){ + return descent; + } + + /** + * Returns leading of this font peer. + */ + public int getLeading(){ + return leading; + } + + /** + * Returns true if this font peer has uniform line metrics. + */ + public boolean hasUniformLineMetrics(){ + return uniformLM; + } + + /** + * Returns type of this font. + * + * @return one of constant font type values. + */ + public int getFontType(){ + return fontType; + } + + /** + * Sets new font type to the font object. + * + * @param newType new type value + */ + public void setFontType(int newType){ + if (newType == FontManager.FONT_TYPE_T1 || newType == FontManager.FONT_TYPE_TT){ + fontType = newType; + } + } + + /** + * Sets new font type to the font object. + * + * @param newType new type value + */ + @Override + protected void finalize() throws Throwable { + super.finalize(); + + dispose(); + } + +} diff --git a/awt/org/apache/harmony/awt/gl/font/FontProperty.java b/awt/org/apache/harmony/awt/gl/font/FontProperty.java new file mode 100644 index 0000000..4eb7cbb --- /dev/null +++ b/awt/org/apache/harmony/awt/gl/font/FontProperty.java @@ -0,0 +1,106 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Ilya S. Okomin + * @version $Revision$ + */ + +package org.apache.harmony.awt.gl.font; + + +/** + * Class containing font property information. This information can be found + * in font.property files. See API documentation, logical fonts description part. + * + */ +public class FontProperty { + + // font file name + String fileName = null; + + // name of the encoding to be used + String encoding = null; + + // array of exclusion ranges (pairs of low and high unicode exclusion bounds) + int[] exclRange = null; + + // font face name + String name = null; + + // font style + int style = -1; + + /** + * Returns font style of this font property. + */ + public int getStyle(){ + return this.style; + } + + /** + * Returns font name of this font property. + */ + public String getName(){ + return this.name; + } + + /** + * Returns encoding used in this font property. + */ + public String getEncoding(){ + return this.encoding; + } + + /** + * Returns an array of exclusion ranges. This array contain pairs of + * low and high bounds of the intervals of characters to ignore in + * total Unicode characters range. + */ + public int[] getExclusionRange(){ + return this.exclRange; + } + + /** + * Returns file name of the font that is described by this font property. + */ + public String getFileName(){ + return this.fileName; + } + + /** + * Returns true if specified character covered by exclusion ranges of this + * font property, false otherwise. + * + * @param ch specified char to check + */ + public boolean isCharExcluded(char ch){ + if (exclRange == null ){ + return false; + } + + for (int i = 0; i < exclRange.length;){ + int lb = exclRange[i++]; + int hb = exclRange[i++]; + + if (ch >= lb && ch <= hb){ + return true; + } + } + + return false; + } +} diff --git a/awt/org/apache/harmony/awt/gl/font/Glyph.java b/awt/org/apache/harmony/awt/gl/font/Glyph.java new file mode 100644 index 0000000..44b8809 --- /dev/null +++ b/awt/org/apache/harmony/awt/gl/font/Glyph.java @@ -0,0 +1,236 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Ilya S. Okomin + * @version $Revision$ + */ +package org.apache.harmony.awt.gl.font; + +import java.awt.Shape; +import java.awt.font.GlyphJustificationInfo; +import java.awt.font.GlyphMetrics; +import java.awt.image.BufferedImage; + +public abstract class Glyph{ + + // character of the glyph + char glChar; + + // precise glyph metrics + GlyphMetrics glMetrics; + + // glyph metrics in pixels + GlyphMetrics glPointMetrics; + + // glyph code of this Glyph + int glCode; + + // justification info of this glyph + GlyphJustificationInfo glJustInfo; + + // native font handle of the font corresponding to this glyph + long pFont; + + // size of the font corresponding to this glyph + int fontSize; + + // bitmap representation of the glyph + byte[] bitmap = null; + + // Buffered image representation of the glyph + BufferedImage image; + + // shape that representing the outline of this glyph + Shape glOutline = null; + + /** + * image bitmap parameters + */ + + // top side bearing + public int bmp_top = 0; + + // left side bearing + public int bmp_left = 0; + + // number of bytes in row + public int bmp_pitch; + + // number of rows + public int bmp_rows; + + // width of the row + public int bmp_width; + + /** + * Retruns handle to Native Font object + */ + public long getPFont(){ + return this.pFont; + } + + /** + * Retruns char value of this glyph object + */ + public char getChar(){ + return glChar; + } + + /** + * Retruns precise width of this glyph object + */ + public int getWidth(){ + return Math.round((float)glMetrics.getBounds2D().getWidth()); + } + + /** + * Retruns precise height of this glyph object + */ + public int getHeight(){ + return Math.round((float)glMetrics.getBounds2D().getHeight()); + } + + /** + * Retruns glyph code of this glyph object + */ + public int getGlyphCode(){ + return glCode; + } + + /** + * Retruns GlyphMetrics of this glyph object with precise metrics. + */ + public GlyphMetrics getGlyphMetrics(){ + return glMetrics; + } + + /** + * Retruns GlyphMetrics of this glyph object in pixels. + */ + public GlyphMetrics getGlyphPointMetrics(){ + return glPointMetrics; + } + + /** + * Retruns GlyphJustificationInfo of this glyph object + */ + public GlyphJustificationInfo getGlyphJustificationInfo(){ + return glJustInfo; + } + + /** + * Sets JustificationInfo of this glyph object + * + * @param newJustInfo GlyphJustificationInfo object to set to the Glyph object + */ + public void setGlyphJustificationInfo(GlyphJustificationInfo newJustInfo){ + this.glJustInfo = newJustInfo; + } + + /** + * Returns an int array of 3 elements, so-called ABC structure that contains + * the width of the character: + * 1st element = left side bearing of the glyph + * 2nd element = width of the glyph + * 3d element = right side bearing of the glyph + */ + public int[] getABC(){ + int[] abc = new int[3]; + abc[0] = (int)glMetrics.getLSB(); + abc[1] = (int)glMetrics.getBounds2D().getWidth(); + abc[2] = (int)glMetrics.getRSB(); + + return abc; + } + + /** + * Sets BufferedImage representation of this glyph to the specified parameter. + * + * @param newImage new BufferedImage object to be set as BufferedImage + * representation. + */ + public void setImage(BufferedImage newImage){ + this.image = newImage; + } + + /** + * Returns true if this Glyph and specified object are equal. + */ + @Override + public boolean equals(Object obj){ + if (obj == this) { + return true; + } + + if (obj != null) { + try { + Glyph gl = (Glyph)obj; + + return ((this.getChar() == gl.getChar()) + && (this.getGlyphMetrics().equals(gl.getGlyphMetrics())) + && (this.getGlyphCode() == gl.getGlyphCode())); + } catch (ClassCastException e) { + } + } + + return false; + } + + /** + * Returns height of the glyph in points. + */ + public int getPointHeight(){ + return (int)glPointMetrics.getBounds2D().getHeight(); + } + + /** + * Returns width of the glyph in points. + */ + public int getPointWidth(){ + return (int)glPointMetrics.getBounds2D().getWidth(); + } + + public Shape getShape(){ + if (glOutline == null){ + glOutline = initOutline(this.glChar); + } + return glOutline; + } + + /** + * Sets BufferedImage representation of this glyph. + */ + public BufferedImage getImage(){ + //!! Implementation classes must override this method + return null; + } + + /** + * Returns array of bytes, representing image of this glyph + */ + public abstract byte[] getBitmap(); + + /** + * Returns shape that represents outline of the specified character. + * + * @param c specified character + */ + public abstract Shape initOutline(char c); + +} + + diff --git a/awt/org/apache/harmony/awt/gl/font/LineMetricsImpl.java b/awt/org/apache/harmony/awt/gl/font/LineMetricsImpl.java new file mode 100644 index 0000000..370146d --- /dev/null +++ b/awt/org/apache/harmony/awt/gl/font/LineMetricsImpl.java @@ -0,0 +1,412 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Ilya S. Okomin + * @version $Revision$ + */ +package org.apache.harmony.awt.gl.font; + +import java.awt.font.LineMetrics; + +import org.apache.harmony.awt.internal.nls.Messages; + +/** + * + * LineMetrics implementation class. + */ + +public class LineMetricsImpl extends LineMetrics implements Cloneable{ + + // array of baseline offsets + float[] baselineOffsets; + + // the number of characters to measure + int numChars; + + // baseline index of the font corresponding to this line metrics + int baseLineIndex; + + // underline thickness + float underlineThickness; + + // underline offset + float underlineOffset; + + // strikethrough thickness + float strikethroughThickness; + + // strikethrough offset + float strikethroughOffset; + + // External leading + float leading; + + // Height of the font ( == (ascent+descent+leading)) + float height; + + // Ascent of the font + float ascent; + + // Descent of the font + float descent; + + // Width of the widest char in the font + float maxCharWidth; + + // underline thickness (in pixels) + int lUnderlineThickness; + + // underline offset (in pixels) + int lUnderlineOffset; + + // strikethrough thickness (in pixels) + int lStrikethroughThickness; + + // strikethrough offset (in pixels) + int lStrikethroughOffset; + + // External leading (in pixels) + int lLeading; + + // Height of the font ( == (ascent+descent+leading)) (in pixels) + int lHeight; + + // Ascent of the font (in pixels) + int lAscent; + + // Descent of the font (in pixels) + int lDescent; + + // Width of the widest char in the font (in pixels) + int lMaxCharWidth; + + // units per EM square in font value + int units_per_EM = 0; + + /** + * Creates LineMetricsImpl object from specified parameters. If baseline data parameter + * is null than {0, (-ascent+descent)/2, -ascent} values are used for baseline offsets. + * + * @param len a number of characters + * @param metrics an array of 16 elements with metrics values that can be + * initialized in native code.

+ * metrics[0] - ascent

+ * metrics[1] - descent

+ * metrics[2] - external leading

+ * metrics[3] - underline thickness

+ * -metrics[4] - underline offset

+ * metrics[5] - strikethrough thickness

+ * -metrics[6] - strikethrough offset

+ * metrics[7] - maximum char width

+ * metrics[8] - ascent in pixels

+ * metrics[9] - descent in pixles

+ * metrics[10] - external leading in pixels

+ * metrics[11] - underline thickness in pixels

+ * -metrics[12] - underline offset in pixels

+ * metrics[13] - strikethrough thickness in pixels

+ * -metrics[14] - strikethrough offset in pixels

+ * metrics[15] - maximum char width in pixels

+ + * @param _baselineData an array of 3 elements with baseline offsets metrics

+ * _baselineData[0] - roman baseline offset

+ * _baselineData[1] - center baseline offset

+ * _baselineData[2] - hanging baseline offset

+ */ + public LineMetricsImpl(int len, float[] metrics, float[] _baselineData){ + numChars = len; + + ascent = metrics[0]; // Ascent of the font + descent = metrics[1]; // Descent of the font + leading = metrics[2]; // External leading + height = metrics[0] + metrics[1] + metrics[2]; // Height of the font ( == (ascent + descent + leading)) + } + + /** + * Creates LineMetricsImpl object from specified parameters. If baseline data parameter + * is null than {0, (-ascent+descent)/2, -ascent} values are used for baseline offsets. + * + * @param _numChars number of chars + * @param _baseLineIndex index of the baseline offset + * @param _baselineOffsets an array of baseline offsets + * @param _underlineThickness underline thickness + * @param _underlineOffset underline offset + * @param _strikethroughThickness strikethrough thickness + * @param _strikethroughOffset strinkethrough offset + * @param _leading leading of the font + * @param _height font height + * @param _ascent ascent of the font + * @param _descent descent of the font + * @param _maxCharWidth max char width + */ + public LineMetricsImpl(int _numChars, int _baseLineIndex, + float[] _baselineOffsets, float _underlineThickness, + float _underlineOffset, float _strikethroughThickness, + float _strikethroughOffset, float _leading, float _height, + float _ascent, float _descent, float _maxCharWidth) { + + numChars = _numChars; + baseLineIndex = _baseLineIndex; + underlineThickness = _underlineThickness; + underlineOffset = _underlineOffset; + strikethroughThickness = _strikethroughThickness; + strikethroughOffset = _strikethroughOffset; + leading = _leading; + height = _height; + ascent = _ascent; + descent = _descent; + baselineOffsets = _baselineOffsets; + lUnderlineThickness = (int) underlineThickness; + lUnderlineOffset = (int) underlineOffset; + lStrikethroughThickness = (int) strikethroughThickness; + lStrikethroughOffset = (int) strikethroughOffset; + lLeading = (int) leading; + lHeight = (int) height; + lAscent = (int) ascent; + lDescent = (int) descent; + maxCharWidth = _maxCharWidth; + } + + public LineMetricsImpl(){ + + } + + /** + * All metrics are scaled according to scaleX and scaleY values. + * This function helps to recompute metrics according to the scale factors + * of desired AffineTransform. + * + * @param scaleX scale X factor + * @param scaleY scale Y factor + */ + public void scale(float scaleX, float scaleY){ + float absScaleX = Math.abs(scaleX); + float absScaleY = Math.abs(scaleY); + + underlineThickness *= absScaleY; + underlineOffset *= scaleY; + strikethroughThickness *= absScaleY; + strikethroughOffset *= scaleY; + leading *= absScaleY; + height *= absScaleY; + ascent *= absScaleY; + descent *= absScaleY; + + if(baselineOffsets == null) { + getBaselineOffsets(); + } + + for (int i=0; i< baselineOffsets.length; i++){ + baselineOffsets[i] *= scaleY; + } + + lUnderlineThickness *= absScaleY; + lUnderlineOffset *= scaleY; + lStrikethroughThickness *= absScaleY; + lStrikethroughOffset *= scaleY; + lLeading *= absScaleY; + lHeight *= absScaleY; + lAscent *= absScaleY; + lDescent *= absScaleY; + maxCharWidth *= absScaleX; + + } + + + /** + * Returns offset of the baseline. + */ + @Override + public float[] getBaselineOffsets() { + // XXX: at the moment there only horizontal metrics are taken into + // account. If there is no baseline information in TrueType font + // file default values used: {0, -ascent, (-ascent+descent)/2} + + return baselineOffsets; + } + + /** + * Returns a number of chars in specified text + */ + @Override + public int getNumChars() { + return numChars; + } + + /** + * Returns index of the baseline, one of predefined constants. + */ + @Override + public int getBaselineIndex() { + // Baseline index is the deafult baseline index value + // taken from the TrueType table "BASE". + return baseLineIndex; + } + + /** + * Returns thickness of the Underline. + */ + @Override + public float getUnderlineThickness() { + return underlineThickness; + } + + /** + * Returns offset of the Underline. + */ + @Override + public float getUnderlineOffset() { + return underlineOffset; + } + + /** + * Returns thickness of the Strikethrough line. + */ + @Override + public float getStrikethroughThickness() { + return strikethroughThickness; + } + + /** + * Returns offset of the Strikethrough line. + */ + @Override + public float getStrikethroughOffset() { + return strikethroughOffset; + } + + /** + * Returns the leading. + */ + @Override + public float getLeading() { + return leading; + } + + /** + * Returns the height of the font. + */ + @Override + public float getHeight() { + //return height; // equals to (ascent + descent + leading); + return ascent + descent + leading; + } + + /** + * Returns the descent. + */ + @Override + public float getDescent() { + return descent; + } + + /** + * Returns the ascent. + */ + @Override + public float getAscent() { + return ascent; + } + + /** + * Returns logical thickness of the Underline. + */ + public int getLogicalUnderlineThickness() { + return lUnderlineThickness; + } + + /** + * Returns logical offset of the Underline. + */ + public int getLogicalUnderlineOffset() { + return lUnderlineOffset; + } + + /** + * Returns logical thickness of the Strikethrough line. + */ + public int getLogicalStrikethroughThickness() { + return lStrikethroughThickness; + } + + /** + * Returns logical offset of the Strikethrough line. + */ + public int getLogicalStrikethroughOffset() { + return lStrikethroughOffset; + } + + /** + * Returns the logical leading. + */ + public int getLogicalLeading() { + return lLeading; + } + + /** + * Returns the logical height of the font. + */ + public int getLogicalHeight() { + return lHeight; // equals to (ascent + descent + leading); + } + + /** + * Returns the logical descent. + */ + public int getLogicalDescent() { + return lDescent; + } + + /** + * Returns the logical ascent. + */ + public int getLogicalAscent() { + return lAscent; + } + + /** + * Returns the logical size of the widest char. + */ + public int getLogicalMaxCharWidth() { + return lMaxCharWidth; + } + + /** + * Returns the size of the widest char. + */ + public float getMaxCharWidth() { + return maxCharWidth; + } + + /** + * Set num chars to the desired value. + * + * @param num specified number of chars + */ + public void setNumChars(int num){ + numChars = num; + } + + @Override + public Object clone(){ + try{ + return super.clone(); + }catch (CloneNotSupportedException e){ + return null; + } + } + +} \ No newline at end of file diff --git a/awt/org/apache/harmony/awt/gl/font/TextDecorator.java b/awt/org/apache/harmony/awt/gl/font/TextDecorator.java new file mode 100644 index 0000000..81905fd --- /dev/null +++ b/awt/org/apache/harmony/awt/gl/font/TextDecorator.java @@ -0,0 +1,433 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* + * @author Oleg V. Khaschansky + * @version $Revision$ + */ + +package org.apache.harmony.awt.gl.font; + +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.Paint; +import java.awt.Shape; +import java.awt.Stroke; +import java.awt.font.TextAttribute; +import java.awt.geom.Area; +import java.awt.geom.Line2D; +import java.awt.geom.Rectangle2D; +import java.text.AttributedCharacterIterator.Attribute; +import java.util.Map; + +/** + * This class is responsible for rendering text decorations like + * underline, strikethrough, text with background, etc. + */ +public class TextDecorator { + private static final TextDecorator inst = new TextDecorator(); + private TextDecorator() {} + static TextDecorator getInstance() { + return inst; + } + + /** + * This class encapsulates a set of decoration attributes for a single text run. + */ + static class Decoration { + private static final BasicStroke UNDERLINE_LOW_ONE_PIXEL_STROKE = + new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10); + + private static final BasicStroke UNDERLINE_LOW_TWO_PIXEL_STROKE = + new BasicStroke(2, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10); + + private static final BasicStroke UNDERLINE_LOW_DOTTED_STROKE = + new BasicStroke( + 1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10, + new float[] { 1, 1 }, 0 + ); + + private static final BasicStroke UNDERLINE_LOW_DOTTED_STROKE2 = + new BasicStroke( + 1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10, + new float[] { 1, 1 }, 1 + ); + + private static final BasicStroke UNDERLINE_LOW_DASHED_STROKE = + new BasicStroke( + 1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10, + new float[] { 4, 4 }, 0 + ); + + boolean ulOn = false; // Have standard underline? + BasicStroke ulStroke; + + BasicStroke imUlStroke; // Stroke for INPUT_METHOD_UNDERLINE + BasicStroke imUlStroke2; // Specially for UNDERLINE_LOW_GRAY + + boolean strikeThrough; + BasicStroke strikeThroughStroke; + + boolean haveStrokes = false; // Strokes already created? + + boolean swapBfFg; + Paint bg; // background color + Paint fg; // foreground color + + Paint graphicsPaint; // Slot for saving current paint + + Decoration( + Integer imUl, + boolean swap, + boolean sth, + Paint bg, Paint fg, + boolean ulOn) { + + if (imUl != null) { + // Determine which stroke to use + if (imUl == TextAttribute.UNDERLINE_LOW_ONE_PIXEL) { + this.imUlStroke = Decoration.UNDERLINE_LOW_ONE_PIXEL_STROKE; + } else if (imUl == TextAttribute.UNDERLINE_LOW_TWO_PIXEL) { + this.imUlStroke = Decoration.UNDERLINE_LOW_TWO_PIXEL_STROKE; + } else if (imUl == TextAttribute.UNDERLINE_LOW_DOTTED) { + this.imUlStroke = Decoration.UNDERLINE_LOW_DOTTED_STROKE; + } else if (imUl == TextAttribute.UNDERLINE_LOW_GRAY) { + this.imUlStroke = Decoration.UNDERLINE_LOW_DOTTED_STROKE; + this.imUlStroke2 = Decoration.UNDERLINE_LOW_DOTTED_STROKE2; + } else if (imUl == TextAttribute.UNDERLINE_LOW_DASHED) { + this.imUlStroke = Decoration.UNDERLINE_LOW_DASHED_STROKE; + } + } + + this.ulOn = ulOn; // Has underline + this.swapBfFg = swap; + this.strikeThrough = sth; + this.bg = bg; + this.fg = fg; + } + + /** + * Creates strokes of proper width according to the info + * stored in the BasicMetrics + * @param metrics - basic metrics + */ + private void getStrokes(BasicMetrics metrics) { + if (!haveStrokes) { + if (strikeThrough) { + strikeThroughStroke = + new BasicStroke( + metrics.strikethroughThickness, + BasicStroke.CAP_BUTT, + BasicStroke.JOIN_MITER, + 10 + ); + } + + if (ulOn) { + ulStroke = + new BasicStroke( + metrics.underlineThickness, + BasicStroke.CAP_BUTT, + BasicStroke.JOIN_MITER, + 10 + ); + } + + haveStrokes = true; + } + } + } + + /** + * Creates Decoration object from the set of text attributes + * @param attributes - text attributes + * @return Decoration object + */ + static Decoration getDecoration(Map attributes) { + if (attributes == null) { + return null; // It is for plain text + } + + Object underline = attributes.get(TextAttribute.UNDERLINE); + boolean hasStandardUnderline = underline == TextAttribute.UNDERLINE_ON; + + Object imUnderline = attributes.get(TextAttribute.INPUT_METHOD_UNDERLINE); + Integer imUl = (Integer) imUnderline; + + boolean swapBgFg = + TextAttribute.SWAP_COLORS_ON.equals( + attributes.get(TextAttribute.SWAP_COLORS) + ); + + boolean strikeThrough = + TextAttribute.STRIKETHROUGH_ON.equals( + attributes.get(TextAttribute.STRIKETHROUGH) + ); + + Paint fg = (Paint) attributes.get(TextAttribute.FOREGROUND); + Paint bg = (Paint) attributes.get(TextAttribute.BACKGROUND); + + if ( + !hasStandardUnderline && + imUnderline == null && + fg == null && + bg == null && + !swapBgFg && + !strikeThrough + ) { + return null; + } + return new Decoration(imUl, swapBgFg, strikeThrough, bg, fg, hasStandardUnderline); + } + + /** + * Fills the background before drawing if needed. + * + * @param trs - text segment + * @param g2d - graphics to draw to + * @param xOffset - offset in X direction to the upper left corner of the + * layout from the origin of the graphics + * @param yOffset - offset in Y direction to the upper left corner of the + * layout from the origin of the graphics + */ + static void prepareGraphics( + TextRunSegment trs, Graphics2D g2d, + float xOffset, float yOffset + ) { + Decoration d = trs.decoration; + + if (d.fg == null && d.bg == null && d.swapBfFg == false) { + return; // Nothing to do + } + + d.graphicsPaint = g2d.getPaint(); + + if (d.fg == null) { + d.fg = d.graphicsPaint; + } + + if (d.swapBfFg) { + // Fill background area + g2d.setPaint(d.fg); + Rectangle2D bgArea = trs.getLogicalBounds(); + Rectangle2D toFill = + new Rectangle2D.Double( + bgArea.getX() + xOffset, + bgArea.getY() + yOffset, + bgArea.getWidth(), + bgArea.getHeight() + ); + g2d.fill(toFill); + + // Set foreground color + g2d.setPaint(d.bg == null ? Color.WHITE : d.bg); + } else { + if (d.bg != null) { // Fill background area + g2d.setPaint(d.bg); + Rectangle2D bgArea = trs.getLogicalBounds(); + Rectangle2D toFill = + new Rectangle2D.Double( + bgArea.getX() + xOffset, + bgArea.getY() + yOffset, + bgArea.getWidth(), + bgArea.getHeight() + ); + g2d.fill(toFill); + } + + // Set foreground color + g2d.setPaint(d.fg); + } + } + + /** + * Restores the original state of the graphics if needed + * @param d - decoration + * @param g2d - graphics + */ + static void restoreGraphics(Decoration d, Graphics2D g2d) { + if (d.fg == null && d.bg == null && d.swapBfFg == false) { + return; // Nothing to do + } + + g2d.setPaint(d.graphicsPaint); + } + + /** + * Renders the text decorations + * @param trs - text run segment + * @param g2d - graphics to render to + * @param xOffset - offset in X direction to the upper left corner + * of the layout from the origin of the graphics + * @param yOffset - offset in Y direction to the upper left corner + * of the layout from the origin of the graphics + */ + static void drawTextDecorations( + TextRunSegment trs, Graphics2D g2d, + float xOffset, float yOffset + ) { + Decoration d = trs.decoration; + + if (!d.ulOn && d.imUlStroke == null && !d.strikeThrough) { + return; // Nothing to do + } + + float left = xOffset + (float) trs.getLogicalBounds().getMinX(); + float right = xOffset + (float) trs.getLogicalBounds().getMaxX(); + + Stroke savedStroke = g2d.getStroke(); + + d.getStrokes(trs.metrics); + + if (d.strikeThrough) { + float y = trs.y + yOffset + trs.metrics.strikethroughOffset; + g2d.setStroke(d.strikeThroughStroke); + g2d.draw(new Line2D.Float(left, y, right, y)); + } + + if (d.ulOn) { + float y = trs.y + yOffset + trs.metrics.underlineOffset; + g2d.setStroke(d.ulStroke); + g2d.draw(new Line2D.Float(left, y, right, y)); + } + + if (d.imUlStroke != null) { + float y = trs.y + yOffset + trs.metrics.underlineOffset; + g2d.setStroke(d.imUlStroke); + g2d.draw(new Line2D.Float(left, y, right, y)); + if (d.imUlStroke2 != null) { + y++; + g2d.setStroke(d.imUlStroke2); + g2d.draw(new Line2D.Float(left, y, right, y)); + } + } + + g2d.setStroke(savedStroke); + } + + /** + * Extends the visual bounds of the text run segment to + * include text decorations. + * @param trs - text segment + * @param segmentBounds - bounds of the undecorated text + * @param d - decoration + * @return extended bounds + */ + static Rectangle2D extendVisualBounds( + TextRunSegment trs, + Rectangle2D segmentBounds, + Decoration d + ) { + if (d == null) { + return segmentBounds; + } + double minx = segmentBounds.getMinX(); + double miny = segmentBounds.getMinY(); + double maxx = segmentBounds.getMaxX(); + double maxy = segmentBounds.getMaxY(); + + Rectangle2D lb = trs.getLogicalBounds(); + + if (d.swapBfFg || d.bg != null) { + minx = Math.min(lb.getMinX() - trs.x, minx); + miny = Math.min(lb.getMinY() - trs.y, miny); + maxx = Math.max(lb.getMaxX() - trs.x, maxx); + maxy = Math.max(lb.getMaxY() - trs.y, maxy); + } + + if (d.ulOn || d.imUlStroke != null || d.strikeThrough) { + minx = Math.min(lb.getMinX() - trs.x, minx); + maxx = Math.max(lb.getMaxX() - trs.x, maxx); + + d.getStrokes(trs.metrics); + + if (d.ulStroke != null) { + maxy = Math.max( + maxy, + trs.metrics.underlineOffset + + d.ulStroke.getLineWidth() + ); + } + + if (d.imUlStroke != null) { + maxy = Math.max( + maxy, + trs.metrics.underlineOffset + + d.imUlStroke.getLineWidth() + + (d.imUlStroke2 == null ? 0 : d.imUlStroke2.getLineWidth()) + ); + } + } + + return new Rectangle2D.Double(minx, miny, maxx-minx, maxy-miny); + } + + /** + * Extends the outline of the text run segment to + * include text decorations. + * @param trs - text segment + * @param segmentOutline - outline of the undecorated text + * @param d - decoration + * @return extended outline + */ + static Shape extendOutline( + TextRunSegment trs, + Shape segmentOutline, + Decoration d + ) { + if (d == null || !d.ulOn && d.imUlStroke == null && !d.strikeThrough) { + return segmentOutline; // Nothing to do + } + + Area res = new Area(segmentOutline); + + float left = (float) trs.getLogicalBounds().getMinX() - trs.x; + float right = (float) trs.getLogicalBounds().getMaxX() - trs.x; + + d.getStrokes(trs.metrics); + + if (d.strikeThrough) { + float y = trs.metrics.strikethroughOffset; + res.add(new Area(d.strikeThroughStroke.createStrokedShape( + new Line2D.Float(left, y, right, y) + ))); + } + + if (d.ulOn) { + float y = trs.metrics.underlineOffset; + res.add(new Area(d.ulStroke.createStrokedShape( + new Line2D.Float(left, y, right, y) + ))); + } + + if (d.imUlStroke != null) { + float y = trs.metrics.underlineOffset; + res.add(new Area(d.imUlStroke.createStrokedShape( + new Line2D.Float(left, y, right, y) + ))); + + if (d.imUlStroke2 != null) { + y++; + res.add(new Area(d.imUlStroke2.createStrokedShape( + new Line2D.Float(left, y, right, y) + ))); + } + } + + return res; + } +} diff --git a/awt/org/apache/harmony/awt/gl/font/TextMetricsCalculator.java b/awt/org/apache/harmony/awt/gl/font/TextMetricsCalculator.java new file mode 100644 index 0000000..be5762a --- /dev/null +++ b/awt/org/apache/harmony/awt/gl/font/TextMetricsCalculator.java @@ -0,0 +1,209 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Oleg V. Khaschansky + * @version $Revision$ + * + */ + +package org.apache.harmony.awt.gl.font; + +import java.awt.font.LineMetrics; +import java.awt.font.GraphicAttribute; +import java.awt.Font; +import java.util.HashMap; +import java.util.ArrayList; + +import org.apache.harmony.awt.internal.nls.Messages; + +/** + * This class operates with an arbitrary text string which can include + * any number of style, font and direction runs. It is responsible for computation + * of the text metrics, such as ascent, descent, leading and advance. Actually, + * each text run segment contains logic which allows it to compute its own metrics and + * responsibility of this class is to combine metrics for all segments included in the text, + * managed by the associated TextRunBreaker object. + */ +public class TextMetricsCalculator { + TextRunBreaker breaker; // Associated run breaker + + // Metrics + float ascent = 0; + float descent = 0; + float leading = 0; + float advance = 0; + + private float baselineOffsets[]; + int baselineIndex; + + public TextMetricsCalculator(TextRunBreaker breaker) { + this.breaker = breaker; + checkBaselines(); + } + + /** + * Returns either values cached by checkBaselines method or reasonable + * values for the TOP and BOTTOM alignments. + * @param baselineIndex - baseline index + * @return baseline offset + */ + float getBaselineOffset(int baselineIndex) { + if (baselineIndex >= 0) { + return baselineOffsets[baselineIndex]; + } else if (baselineIndex == GraphicAttribute.BOTTOM_ALIGNMENT) { + return descent; + } else if (baselineIndex == GraphicAttribute.TOP_ALIGNMENT) { + return -ascent; + } else { + // awt.3F=Invalid baseline index + throw new IllegalArgumentException(Messages.getString("awt.3F")); //$NON-NLS-1$ + } + } + + public float[] getBaselineOffsets() { + float ret[] = new float[baselineOffsets.length]; + System.arraycopy(baselineOffsets, 0, ret, 0, baselineOffsets.length); + return ret; + } + + /** + * Take baseline offsets from the first font or graphic attribute + * and normalizes them, than caches the results. + */ + public void checkBaselines() { + // Take baseline offsets of the first font and normalize them + HashMap fonts = breaker.fonts; + + Object val = fonts.get(new Integer(0)); + + if (val instanceof Font) { + Font firstFont = (Font) val; + LineMetrics lm = firstFont.getLineMetrics(breaker.text, 0, 1, breaker.frc); + baselineOffsets = lm.getBaselineOffsets(); + baselineIndex = lm.getBaselineIndex(); + } else if (val instanceof GraphicAttribute) { + // Get first graphic attribute and use it + GraphicAttribute ga = (GraphicAttribute) val; + + int align = ga.getAlignment(); + + if ( + align == GraphicAttribute.TOP_ALIGNMENT || + align == GraphicAttribute.BOTTOM_ALIGNMENT + ) { + baselineIndex = GraphicAttribute.ROMAN_BASELINE; + } else { + baselineIndex = align; + } + + baselineOffsets = new float[3]; + baselineOffsets[0] = 0; + baselineOffsets[1] = (ga.getDescent() - ga.getAscent()) / 2.f; + baselineOffsets[2] = -ga.getAscent(); + } else { // Use defaults - Roman baseline and zero offsets + baselineIndex = GraphicAttribute.ROMAN_BASELINE; + baselineOffsets = new float[3]; + } + + // Normalize offsets if needed + if (baselineOffsets[baselineIndex] != 0) { + float baseOffset = baselineOffsets[baselineIndex]; + for (int i = 0; i < baselineOffsets.length; i++) { + baselineOffsets[i] -= baseOffset; + } + } + } + + /** + * Computes metrics for the text managed by the associated TextRunBreaker + */ + void computeMetrics() { + + ArrayList segments = breaker.runSegments; + + float maxHeight = 0; + float maxHeightLeading = 0; + + for (int i = 0; i < segments.size(); i++) { + TextRunSegment segment = segments.get(i); + BasicMetrics metrics = segment.metrics; + int baseline = metrics.baseLineIndex; + + if (baseline >= 0) { + float baselineOffset = baselineOffsets[metrics.baseLineIndex]; + float fixedDescent = metrics.descent + baselineOffset; + + ascent = Math.max(ascent, metrics.ascent - baselineOffset); + descent = Math.max(descent, fixedDescent); + leading = Math.max(leading, fixedDescent + metrics.leading); + } else { // Position is not fixed by the baseline, need sum of ascent and descent + float height = metrics.ascent + metrics.descent; + + maxHeight = Math.max(maxHeight, height); + maxHeightLeading = Math.max(maxHeightLeading, height + metrics.leading); + } + } + + // Need to increase sizes for graphics? + if (maxHeightLeading != 0) { + descent = Math.max(descent, maxHeight - ascent); + leading = Math.max(leading, maxHeightLeading - ascent); + } + + // Normalize leading + leading -= descent; + + BasicMetrics currMetrics; + float currAdvance = 0; + + for (int i = 0; i < segments.size(); i++) { + TextRunSegment segment = segments.get(breaker.getSegmentFromVisualOrder(i)); + currMetrics = segment.metrics; + + segment.y = getBaselineOffset(currMetrics.baseLineIndex) + + currMetrics.superScriptOffset; + segment.x = currAdvance; + + currAdvance += segment.getAdvance(); + } + + advance = currAdvance; + } + + /** + * Computes metrics and creates BasicMetrics object from them + * @return basic metrics + */ + public BasicMetrics createMetrics() { + computeMetrics(); + return new BasicMetrics(this); + } + + /** + * Corrects advance after justification. Gets BasicMetrics object + * and updates advance stored into it. + * @param metrics - metrics with outdated advance which should be corrected + */ + public void correctAdvance(BasicMetrics metrics) { + ArrayList segments = breaker.runSegments; + TextRunSegment segment = segments.get(breaker + .getSegmentFromVisualOrder(segments.size() - 1)); + + advance = segment.x + segment.getAdvance(); + metrics.advance = advance; + } +} diff --git a/awt/org/apache/harmony/awt/gl/font/TextRunBreaker.java b/awt/org/apache/harmony/awt/gl/font/TextRunBreaker.java new file mode 100644 index 0000000..be606f7 --- /dev/null +++ b/awt/org/apache/harmony/awt/gl/font/TextRunBreaker.java @@ -0,0 +1,861 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Oleg V. Khaschansky + * @version $Revision$ + * + */ + +package org.apache.harmony.awt.gl.font; + + +import java.awt.geom.GeneralPath; +import java.awt.geom.Rectangle2D; +import java.awt.im.InputMethodHighlight; +import java.awt.font.*; +import java.awt.*; +import java.text.AttributedCharacterIterator; +import java.text.Annotation; +import java.text.AttributedCharacterIterator.Attribute; +import java.util.*; + +import org.apache.harmony.awt.gl.font.TextDecorator.Decoration; +import org.apache.harmony.awt.internal.nls.Messages; +import org.apache.harmony.misc.HashCode; +// TODO - bidi not implemented yet + +/** + * This class is responsible for breaking the text into the run segments + * with constant font, style, other text attributes and direction. + * It also stores the created text run segments and covers functionality + * related to the operations on the set of segments, like calculating metrics, + * rendering, justification, hit testing, etc. + */ +public class TextRunBreaker implements Cloneable { + AttributedCharacterIterator aci; + FontRenderContext frc; + + char[] text; + + byte[] levels; + + HashMap fonts; + HashMap decorations; + + // Related to default font substitution + int forcedFontRunStarts[]; + + ArrayList runSegments = new ArrayList(); + + // For fast retrieving of the segment containing + // character with known logical index + int logical2segment[]; + int segment2visual[]; // Visual order of segments TODO - implement + int visual2segment[]; + int logical2visual[]; + int visual2logical[]; + + SegmentsInfo storedSegments; + private boolean haveAllSegments = false; + int segmentsStart, segmentsEnd; + + float justification = 1.0f; + + public TextRunBreaker(AttributedCharacterIterator aci, FontRenderContext frc) { + this.aci = aci; + this.frc = frc; + + segmentsStart = aci.getBeginIndex(); + segmentsEnd = aci.getEndIndex(); + + int len = segmentsEnd - segmentsStart; + text = new char[len]; + aci.setIndex(segmentsEnd); + while (len-- != 0) { // Going in backward direction is faster? Simplier checks here? + text[len] = aci.previous(); + } + + createStyleRuns(); + } + + /** + * Visual order of text segments may differ from the logical order. + * This method calculates visual position of the segment from its logical position. + * @param segmentNum - logical position of the segment + * @return visual position of the segment + */ + int getVisualFromSegmentOrder(int segmentNum) { + return (segment2visual == null) ? segmentNum : segment2visual[segmentNum]; + } + + /** + * Visual order of text segments may differ from the logical order. + * This method calculates logical position of the segment from its visual position. + * @param visual - visual position of the segment + * @return logical position of the segment + */ + int getSegmentFromVisualOrder(int visual) { + return (visual2segment == null) ? visual : visual2segment[visual]; + } + + /** + * Visual order of the characters may differ from the logical order. + * This method calculates visual position of the character from its logical position. + * @param logical - logical position of the character + * @return visual position + */ + int getVisualFromLogical(int logical) { + return (logical2visual == null) ? logical : logical2visual[logical]; + } + + /** + * Visual order of the characters may differ from the logical order. + * This method calculates logical position of the character from its visual position. + * @param visual - visual position + * @return logical position + */ + int getLogicalFromVisual(int visual) { + return (visual2logical == null) ? visual : visual2logical[visual]; + } + + /** + * Calculates the end index of the level run, limited by the given text run. + * @param runStart - run start + * @param runEnd - run end + * @return end index of the level run + */ + int getLevelRunLimit(int runStart, int runEnd) { + if (levels == null) { + return runEnd; + } + int endLevelRun = runStart + 1; + byte level = levels[runStart]; + + while (endLevelRun <= runEnd && levels[endLevelRun] == level) { + endLevelRun++; + } + + return endLevelRun; + } + + /** + * Adds InputMethodHighlight to the attributes + * @param attrs - text attributes + * @return patched text attributes + */ + Map unpackAttributes(Map attrs) { + if (attrs.containsKey(TextAttribute.INPUT_METHOD_HIGHLIGHT)) { + Map styles = null; + + Object val = attrs.get(TextAttribute.INPUT_METHOD_HIGHLIGHT); + + if (val instanceof Annotation) { + val = ((Annotation) val).getValue(); + } + + if (val instanceof InputMethodHighlight) { + InputMethodHighlight ihl = ((InputMethodHighlight) val); + styles = ihl.getStyle(); + + if (styles == null) { + Toolkit tk = Toolkit.getDefaultToolkit(); + styles = tk.mapInputMethodHighlight(ihl); + } + } + + if (styles != null) { + HashMap newAttrs = new HashMap(); + newAttrs.putAll(attrs); + newAttrs.putAll(styles); + return newAttrs; + } + } + + return attrs; + } + + /** + * Breaks the text into separate style runs. + */ + void createStyleRuns() { + // TODO - implement fast and simple case + fonts = new HashMap(); + decorations = new HashMap(); + //// + + ArrayList forcedFontRunStartsList = null; + + Map attributes = null; + + // Check justification attribute + Object val = aci.getAttribute(TextAttribute.JUSTIFICATION); + if (val != null) { + justification = ((Float) val).floatValue(); + } + + for ( + int index = segmentsStart, nextRunStart = segmentsStart; + index < segmentsEnd; + index = nextRunStart, aci.setIndex(index) + ) { + nextRunStart = aci.getRunLimit(); + attributes = unpackAttributes(aci.getAttributes()); + + TextDecorator.Decoration d = TextDecorator.getDecoration(attributes); + decorations.put(new Integer(index), d); + + // Find appropriate font or place GraphicAttribute there + + // 1. Try to pick up CHAR_REPLACEMENT (compatibility) + Font value = (Font)attributes.get(TextAttribute.CHAR_REPLACEMENT); + + if (value == null) { + // 2. Try to Get FONT + value = (Font)attributes.get(TextAttribute.FONT); + + if (value == null) { + // 3. Try to create font from FAMILY + if (attributes.get(TextAttribute.FAMILY) != null) { + value = Font.getFont(attributes); + } + + if (value == null) { + // 4. No attributes found, using default. + if (forcedFontRunStartsList == null) { + forcedFontRunStartsList = new ArrayList(); + } + FontFinder.findFonts( + text, + index, + nextRunStart, + forcedFontRunStartsList, + fonts + ); + value = fonts.get(new Integer(index)); + } + } + } + + fonts.put(new Integer(index), value); + } + + // We have added some default fonts, so we have some extra runs in text + if (forcedFontRunStartsList != null) { + forcedFontRunStarts = new int[forcedFontRunStartsList.size()]; + for (int i=0; i runStart) { + maxPos = Math.min(element, maxPos); + break; + } + } + } + + return Math.min(aci.getRunLimit(), maxPos); + } + + /** + * Creates segments for the text run with + * constant decoration, font and bidi level + * @param runStart - run start + * @param runEnd - run end + */ + public void createSegments(int runStart, int runEnd) { + int endStyleRun, endLevelRun; + + // TODO - update levels + + int pos = runStart, levelPos; + + aci.setIndex(pos); + final int firstRunStart = aci.getRunStart(); + Object tdd = decorations.get(new Integer(firstRunStart)); + Object fontOrGAttr = fonts.get(new Integer(firstRunStart)); + + logical2segment = new int[runEnd - runStart]; + + do { + endStyleRun = getStyleRunLimit(pos, runEnd); + + // runStart can be non-zero, but all arrays will be indexed from 0 + int ajustedPos = pos - runStart; + int ajustedEndStyleRun = endStyleRun - runStart; + levelPos = ajustedPos; + do { + endLevelRun = getLevelRunLimit(levelPos, ajustedEndStyleRun); + + if (fontOrGAttr instanceof GraphicAttribute) { + runSegments.add( + new TextRunSegmentImpl.TextRunSegmentGraphic( + (GraphicAttribute)fontOrGAttr, + endLevelRun - levelPos, + levelPos + runStart) + ); + Arrays.fill(logical2segment, levelPos, endLevelRun, runSegments.size()-1); + } else { + TextRunSegmentImpl.TextSegmentInfo i = + new TextRunSegmentImpl.TextSegmentInfo( + levels == null ? 0 : levels[ajustedPos], + (Font) fontOrGAttr, + frc, + text, + levelPos + runStart, + endLevelRun + runStart + ); + + runSegments.add( + new TextRunSegmentImpl.TextRunSegmentCommon( + i, + (TextDecorator.Decoration) tdd + ) + ); + Arrays.fill(logical2segment, levelPos, endLevelRun, runSegments.size()-1); + } + + levelPos = endLevelRun; + } while (levelPos < ajustedEndStyleRun); + + // Prepare next iteration + pos = endStyleRun; + tdd = decorations.get(new Integer(pos)); + fontOrGAttr = fonts.get(new Integer(pos)); + } while (pos < runEnd); + } + + /** + * Checks if text run segments are up to date and creates the new segments if not. + */ + public void createAllSegments() { + if ( !haveAllSegments && + (logical2segment == null || + logical2segment.length != segmentsEnd - segmentsStart) + ) { // Check if we don't have all segments yet + resetSegments(); + createSegments(segmentsStart, segmentsEnd); + } + + haveAllSegments = true; + } + + /** + * Calculates position where line should be broken without + * taking into account word boundaries. + * @param start - start index + * @param maxAdvance - maximum advance, width of the line + * @return position where to break + */ + public int getLineBreakIndex(int start, float maxAdvance) { + int breakIndex; + TextRunSegment s = null; + + for ( + int segmentIndex = logical2segment[start]; + segmentIndex < runSegments.size(); + segmentIndex++ + ) { + s = runSegments.get(segmentIndex); + breakIndex = s.getCharIndexFromAdvance(maxAdvance, start); + + if (breakIndex < s.getEnd()) { + return breakIndex; + } + maxAdvance -= s.getAdvanceDelta(start, s.getEnd()); + start = s.getEnd(); + } + + return s.getEnd(); + } + + /** + * Inserts character into the managed text. + * @param newParagraph - new character iterator + * @param insertPos - insertion position + */ + public void insertChar(AttributedCharacterIterator newParagraph, int insertPos) { + aci = newParagraph; + + char insChar = aci.setIndex(insertPos); + + Integer key = new Integer(insertPos); + + insertPos -= aci.getBeginIndex(); + + char newText[] = new char[text.length + 1]; + System.arraycopy(text, 0, newText, 0, insertPos); + newText[insertPos] = insChar; + System.arraycopy(text, insertPos, newText, insertPos+1, text.length - insertPos); + text = newText; + + if (aci.getRunStart() == key.intValue() && aci.getRunLimit() == key.intValue() + 1) { + createStyleRuns(); // We have to create one new run, could be optimized + } else { + shiftStyleRuns(key, 1); + } + + resetSegments(); + + segmentsEnd++; + } + + /** + * Deletes character from the managed text. + * @param newParagraph - new character iterator + * @param deletePos - deletion position + */ + public void deleteChar(AttributedCharacterIterator newParagraph, int deletePos) { + aci = newParagraph; + + Integer key = new Integer(deletePos); + + deletePos -= aci.getBeginIndex(); + + char newText[] = new char[text.length - 1]; + System.arraycopy(text, 0, newText, 0, deletePos); + System.arraycopy(text, deletePos+1, newText, deletePos, newText.length - deletePos); + text = newText; + + if (fonts.get(key) != null) { + fonts.remove(key); + } + + shiftStyleRuns(key, -1); + + resetSegments(); + + segmentsEnd--; + } + + /** + * Shift all runs after specified position, needed to perfom insertion + * or deletion in the managed text + * @param pos - position where to start + * @param shift - shift, could be negative + */ + private void shiftStyleRuns(Integer pos, final int shift) { + ArrayList keys = new ArrayList(); + + Integer key, oldkey; + for (Iterator it = fonts.keySet().iterator(); it.hasNext(); ) { + oldkey = it.next(); + if (oldkey.intValue() > pos.intValue()) { + keys.add(oldkey); + } + } + + for (int i=0; i(); + logical2segment = null; + segment2visual = null; + visual2segment = null; + levels = null; + haveAllSegments = false; + } + + private class SegmentsInfo { + ArrayList runSegments; + int logical2segment[]; + int segment2visual[]; + int visual2segment[]; + byte levels[]; + int segmentsStart; + int segmentsEnd; + } + + /** + * Saves the internal state of the class + * @param newSegStart - new start index in the text + * @param newSegEnd - new end index in the text + */ + public void pushSegments(int newSegStart, int newSegEnd) { + storedSegments = new SegmentsInfo(); + storedSegments.runSegments = this.runSegments; + storedSegments.logical2segment = this.logical2segment; + storedSegments.segment2visual = this.segment2visual; + storedSegments.visual2segment = this.visual2segment; + storedSegments.levels = this.levels; + storedSegments.segmentsStart = segmentsStart; + storedSegments.segmentsEnd = segmentsEnd; + + resetSegments(); + + segmentsStart = newSegStart; + segmentsEnd = newSegEnd; + } + + /** + * Restores the internal state of the class + */ + public void popSegments() { + if (storedSegments == null) { + return; + } + + this.runSegments = storedSegments.runSegments; + this.logical2segment = storedSegments.logical2segment; + this.segment2visual = storedSegments.segment2visual; + this.visual2segment = storedSegments.visual2segment; + this.levels = storedSegments.levels; + this.segmentsStart = storedSegments.segmentsStart; + this.segmentsEnd = storedSegments.segmentsEnd; + storedSegments = null; + + if (runSegments.size() == 0 && logical2segment == null) { + haveAllSegments = false; + } else { + haveAllSegments = true; + } + } + + @Override + public Object clone() { + try { + TextRunBreaker res = (TextRunBreaker) super.clone(); + res.storedSegments = null; + ArrayList newSegments = new ArrayList(runSegments.size()); + for (int i = 0; i < runSegments.size(); i++) { + TextRunSegment seg = runSegments.get(i); + newSegments.add((TextRunSegment)seg.clone()); + } + res.runSegments = newSegments; + return res; + } catch (CloneNotSupportedException e) { + // awt.3E=Clone not supported + throw new UnsupportedOperationException(Messages.getString("awt.3E")); //$NON-NLS-1$ + } + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof TextRunBreaker)) { + return false; + } + + TextRunBreaker br = (TextRunBreaker) obj; + + if (br.getACI().equals(aci) && br.frc.equals(frc)) { + return true; + } + + return false; + } + + @Override + public int hashCode() { + return HashCode.combine(aci.hashCode(), frc.hashCode()); + } + + /** + * Renders the managed text + * @param g2d - graphics where to render + * @param xOffset - offset in X direction to the upper left corner + * of the layout from the origin of the graphics + * @param yOffset - offset in Y direction to the upper left corner + * of the layout from the origin of the graphics + */ + public void drawSegments(Graphics2D g2d, float xOffset, float yOffset) { + for (int i=0; i= x) || // We are in the segment + (endOfPrevSeg < x && bounds.getMinX() > x)) { // We are somewhere between the segments + return segment.hitTest(x,y); + } + endOfPrevSeg = bounds.getMaxX(); + } + + return isLTR() ? TextHitInfo.trailing(text.length) : TextHitInfo.leading(0); + } + + public float getJustification() { + return justification; + } + + /** + * Calculates position of the last non whitespace character + * in the managed text. + * @return position of the last non whitespace character + */ + public int getLastNonWhitespace() { + int lastNonWhitespace = text.length; + + while (lastNonWhitespace >= 0) { + lastNonWhitespace--; + if (!Character.isWhitespace(text[lastNonWhitespace])) { + break; + } + } + + return lastNonWhitespace; + } + + /** + * Performs justification of the managed text by changing segment positions + * and positions of the glyphs inside of the segments. + * @param gap - amount of space which should be compensated by justification + */ + public void justify(float gap) { + // Ignore trailing logical whitespace + int firstIdx = segmentsStart; + int lastIdx = getLastNonWhitespace() + segmentsStart; + JustificationInfo jInfos[] = new JustificationInfo[5]; + float gapLeft = gap; + + int highestPriority = -1; + // GlyphJustificationInfo.PRIORITY_KASHIDA is 0 + // GlyphJustificationInfo.PRIORITY_NONE is 3 + for (int priority = 0; priority <= GlyphJustificationInfo.PRIORITY_NONE + 1; priority++) { + JustificationInfo jInfo = new JustificationInfo(); + jInfo.lastIdx = lastIdx; + jInfo.firstIdx = firstIdx; + jInfo.grow = gap > 0; + jInfo.gapToFill = gapLeft; + + if (priority <= GlyphJustificationInfo.PRIORITY_NONE) { + jInfo.priority = priority; + } else { + jInfo.priority = highestPriority; // Last pass + } + + for (int i = 0; i < runSegments.size(); i++) { + TextRunSegment segment = runSegments.get(i); + if (segment.getStart() <= lastIdx) { + segment.updateJustificationInfo(jInfo); + } + } + + if (jInfo.priority == highestPriority) { + jInfo.absorb = true; + jInfo.absorbedWeight = jInfo.weight; + } + + if (jInfo.weight != 0) { + if (highestPriority < 0) { + highestPriority = priority; + } + jInfos[priority] = jInfo; + } else { + continue; + } + + gapLeft -= jInfo.growLimit; + + if (((gapLeft > 0) ^ jInfo.grow) || gapLeft == 0) { + gapLeft = 0; + jInfo.gapPerUnit = jInfo.gapToFill/jInfo.weight; + break; + } + jInfo.useLimits = true; + + if (jInfo.absorbedWeight > 0) { + jInfo.absorb = true; + jInfo.absorbedGapPerUnit = + (jInfo.gapToFill-jInfo.growLimit)/jInfo.absorbedWeight; + break; + } + } + + float currJustificationOffset = 0; + for (int i = 0; i < runSegments.size(); i++) { + TextRunSegment segment = + runSegments.get(getSegmentFromVisualOrder(i)); + segment.x += currJustificationOffset; + currJustificationOffset += segment.doJustification(jInfos); + } + + justification = -1; // Make further justification impossible + } + + /** + * This class represents the information collected before the actual + * justification is started and needed to perform the justification. + * This information is closely related to the information stored in the + * GlyphJustificationInfo for the text represented by glyph vectors. + */ + class JustificationInfo { + boolean grow; + boolean absorb = false; + boolean useLimits = false; + int priority = 0; + float weight = 0; + float absorbedWeight = 0; + float growLimit = 0; + + int lastIdx; + int firstIdx; + + float gapToFill; + + float gapPerUnit = 0; // Precalculated value, gapToFill / weight + float absorbedGapPerUnit = 0; // Precalculated value, gapToFill / weight + } +} diff --git a/awt/org/apache/harmony/awt/gl/font/TextRunSegment.java b/awt/org/apache/harmony/awt/gl/font/TextRunSegment.java new file mode 100644 index 0000000..1cd2c05 --- /dev/null +++ b/awt/org/apache/harmony/awt/gl/font/TextRunSegment.java @@ -0,0 +1,165 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* + * @author Oleg V. Khaschansky + * @version $Revision$ + */ + +package org.apache.harmony.awt.gl.font; + +import java.awt.Graphics2D; +import java.awt.Shape; +import java.awt.font.TextHitInfo; +import java.awt.geom.Rectangle2D; + +/** + * Abstract class which represents the segment of the text with constant attributes + * running in one direction (i.e. constant level). + */ +public abstract class TextRunSegment implements Cloneable { + float x; // Calculated x location of this segment on the screen + float y; // Calculated y location of this segment on the screen + + BasicMetrics metrics; // Metrics of this text run segment + TextDecorator.Decoration decoration; // Underline, srikethrough, etc. + Rectangle2D logicalBounds = null; // Logical bounding box for the segment + Rectangle2D visualBounds = null; // Visual bounding box for the segment + + /** + * Returns start index of the segment + * @return start index + */ + abstract int getStart(); + + /** + * Returns end index of the segment + * @return end index + */ + abstract int getEnd(); + + /** + * Returns the number of characters in the segment + * @return number of characters + */ + abstract int getLength(); + + /** + * Renders this text run segment + * @param g2d - graphics to render to + * @param xOffset - X offset from the graphics origin to the + * origin of the text layout + * @param yOffset - Y offset from the graphics origin to the + * origin of the text layout + */ + abstract void draw(Graphics2D g2d, float xOffset, float yOffset); + + /** + * Creates black box bounds shape for the specified range + * @param start - range sart + * @param limit - range end + * @return black box bounds shape + */ + abstract Shape getCharsBlackBoxBounds(int start, int limit); + + /** + * Returns the outline shape + * @return outline + */ + abstract Shape getOutline(); + + /** + * Returns visual bounds of this segment + * @return visual bounds + */ + abstract Rectangle2D getVisualBounds(); + + /** + * Returns logical bounds of this segment + * @return logical bounds + */ + abstract Rectangle2D getLogicalBounds(); + + /** + * Calculates advance of the segment + * @return advance + */ + abstract float getAdvance(); + + /** + * Calculates advance delta between two characters + * @param start - 1st position + * @param end - 2nd position + * @return advance increment between specified positions + */ + abstract float getAdvanceDelta(int start, int end); + + /** + * Calculates index of the character which advance is equal to + * the given. If the given advance is greater then the segment + * advance it returns the position after the last character. + * @param advance - given advance + * @param start - character, from which to start measuring advance + * @return character index + */ + abstract int getCharIndexFromAdvance(float advance, int start); + + /** + * Checks if the character doesn't contribute to the text advance + * @param index - character index + * @return true if the character has zero advance + */ + abstract boolean charHasZeroAdvance(int index); + + /** + * Calculates position of the character on the screen + * @param index - character index + * @return X coordinate of the character position + */ + abstract float getCharPosition(int index); + + /** + * Returns the advance of the individual character + * @param index - character index + * @return character advance + */ + abstract float getCharAdvance(int index); + + /** + * Creates text hit info from the hit position + * @param x - X coordinate relative to the origin of the layout + * @param y - Y coordinate relative to the origin of the layout + * @return hit info + */ + abstract TextHitInfo hitTest(float x, float y); + + /** + * Collects justification information into JustificationInfo object + * @param jInfo - JustificationInfo object + */ + abstract void updateJustificationInfo(TextRunBreaker.JustificationInfo jInfo); + + /** + * Performs justification of the segment. + * Updates positions of individual characters. + * @param jInfos - justification information, gathered by the previous passes + * @return amount of growth or shrink of the segment + */ + abstract float doJustification(TextRunBreaker.JustificationInfo jInfos[]); + + @Override + public abstract Object clone(); +} diff --git a/awt/org/apache/harmony/awt/gl/font/TextRunSegmentImpl.java b/awt/org/apache/harmony/awt/gl/font/TextRunSegmentImpl.java new file mode 100644 index 0000000..0ec2d05 --- /dev/null +++ b/awt/org/apache/harmony/awt/gl/font/TextRunSegmentImpl.java @@ -0,0 +1,979 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Oleg V. Khaschansky + * @version $Revision$ + * + */ + +package org.apache.harmony.awt.gl.font; + +import java.awt.*; +import java.awt.font.*; +import java.awt.geom.Rectangle2D; +import java.awt.geom.GeneralPath; +import java.awt.geom.AffineTransform; +import java.awt.geom.Point2D; +// XXX - TODO - bidi not implemented yet +//import java.text.Bidi; +import java.util.Arrays; + +import org.apache.harmony.awt.internal.nls.Messages; + +/** + * Date: Apr 25, 2005 + * Time: 4:33:18 PM + * + * This class contains the implementation of the behavior of the + * text run segment with constant text attributes and direction. + */ +public class TextRunSegmentImpl { + + /** + * This class contains basic information required for creation + * of the glyph-based text run segment. + */ + public static class TextSegmentInfo { + // XXX - TODO - bidi not implemented yet + //Bidi bidi; + + Font font; + FontRenderContext frc; + + char text[]; + + int start; + int end; + int length; + + int flags = 0; + + byte level = 0; + + TextSegmentInfo( + byte level, + Font font, FontRenderContext frc, + char text[], int start, int end + ) { + this.font = font; + this.frc = frc; + this.text = text; + this.start = start; + this.end = end; + this.level = level; + length = end - start; + } + } + + /** + * This class represents a simple text segment backed by the glyph vector + */ + public static class TextRunSegmentCommon extends TextRunSegment { + TextSegmentInfo info; + private GlyphVector gv; + private float advanceIncrements[]; + private int char2glyph[]; + private GlyphJustificationInfo gjis[]; // Glyph justification info + + TextRunSegmentCommon(TextSegmentInfo i, TextDecorator.Decoration d) { + // XXX - todo - check support bidi + i.flags &= ~0x09; // Clear bidi flags + + if ((i.level & 0x1) != 0) { + i.flags |= Font.LAYOUT_RIGHT_TO_LEFT; + } + + info = i; + this.decoration = d; + + LineMetrics lm = i.font.getLineMetrics(i.text, i.start, i.end, i.frc); + this.metrics = new BasicMetrics(lm, i.font); + + if (lm.getNumChars() != i.length) { // XXX todo - This should be handled + // awt.41=Font returned unsupported type of line metrics. This case is known, but not supported yet. + throw new UnsupportedOperationException( + Messages.getString("awt.41")); //$NON-NLS-1$ + } + } + + @Override + public Object clone() { + return new TextRunSegmentCommon(info, decoration); + } + + /** + * Creates glyph vector from the managed text if needed + * @return glyph vector + */ + private GlyphVector getGlyphVector() { + if (gv==null) { + gv = info.font.layoutGlyphVector( + info.frc, + info.text, + info.start, + info.end - info.start, // NOTE: This parameter violates + // spec, it is count, + // not limit as spec states + info.flags + ); + } + + return gv; + } + + /** + * Renders this text run segment + * @param g2d - graphics to render to + * @param xOffset - X offset from the graphics origin to the + * origin of the text layout + * @param yOffset - Y offset from the graphics origin to the + * origin of the text layout + */ + @Override + void draw(Graphics2D g2d, float xOffset, float yOffset) { + if (decoration == null) { + g2d.drawGlyphVector(getGlyphVector(), xOffset + x, yOffset + y); + } else { + TextDecorator.prepareGraphics(this, g2d, xOffset, yOffset); + g2d.drawGlyphVector(getGlyphVector(), xOffset + x, yOffset + y); + TextDecorator.drawTextDecorations(this, g2d, xOffset, yOffset); + TextDecorator.restoreGraphics(decoration, g2d); + } + } + + /** + * Returns visual bounds of this segment + * @return visual bounds + */ + @Override + Rectangle2D getVisualBounds() { + if (visualBounds == null) { + visualBounds = + TextDecorator.extendVisualBounds( + this, + getGlyphVector().getVisualBounds(), + decoration + ); + + visualBounds.setRect( + x + visualBounds.getX(), + y + visualBounds.getY(), + visualBounds.getWidth(), + visualBounds.getHeight() + ); + } + + return (Rectangle2D) visualBounds.clone(); + } + + /** + * Returns logical bounds of this segment + * @return logical bounds + */ + @Override + Rectangle2D getLogicalBounds() { + if (logicalBounds == null) { + logicalBounds = getGlyphVector().getLogicalBounds(); + + logicalBounds.setRect( + x + logicalBounds.getX(), + y + logicalBounds.getY(), + logicalBounds.getWidth(), + logicalBounds.getHeight() + ); + } + + return (Rectangle2D) logicalBounds.clone(); + } + + @Override + float getAdvance() { + return (float) getLogicalBounds().getWidth(); + } + + /** + * Attemts to map each character to the corresponding advance increment + */ + void initAdvanceMapping() { + GlyphVector gv = getGlyphVector(); + int charIndicies[] = gv.getGlyphCharIndices(0, gv.getNumGlyphs(), null); + advanceIncrements = new float[info.length]; + + for (int i=0; i info.length) { + end = info.length; + } + + float sum = 0; + for (int i=start; i info.length) { + limit = info.length; + } + + GeneralPath result = new GeneralPath(); + + int glyphIndex = 0; + + for (int i=start; i info.length) { + index = info.length; + } + + float result = 0; + + int glyphIndex = getChar2Glyph()[index]; + result = (float) getGlyphVector().getGlyphPosition(glyphIndex).getX(); + + // Shift to the segment's coordinates + result += x; + + return result; + } + + /** + * Returns the advance of the individual character + * @param index - character index + * @return character advance + */ + @Override + float getCharAdvance(int index) { + if (advanceIncrements == null) { + initAdvanceMapping(); + } + + return advanceIncrements[index - this.getStart()]; + } + + /** + * Returns the outline shape + * @return outline + */ + @Override + Shape getOutline() { + AffineTransform t = AffineTransform.getTranslateInstance(x, y); + return t.createTransformedShape( + TextDecorator.extendOutline( + this, + getGlyphVector().getOutline(), + decoration + ) + ); + } + + /** + * Checks if the character doesn't contribute to the text advance + * @param index - character index + * @return true if the character has zero advance + */ + @Override + boolean charHasZeroAdvance(int index) { + if (advanceIncrements == null) { + initAdvanceMapping(); + } + + return advanceIncrements[index - this.getStart()] == 0; + } + + /** + * Creates text hit info from the hit position + * @param hitX - X coordinate relative to the origin of the layout + * @param hitY - Y coordinate relative to the origin of the layout + * @return hit info + */ + @Override + TextHitInfo hitTest(float hitX, float hitY) { + hitX -= x; + + float glyphPositions[] = + getGlyphVector().getGlyphPositions(0, info.length+1, null); + + int glyphIdx; + boolean leading = false; + for (glyphIdx = 1; glyphIdx <= info.length; glyphIdx++) { + if (glyphPositions[(glyphIdx)*2] >= hitX) { + float advance = + glyphPositions[(glyphIdx)*2] - glyphPositions[(glyphIdx-1)*2]; + leading = glyphPositions[(glyphIdx-1)*2] + advance/2 > hitX ? true : false; + glyphIdx--; + break; + } + } + + if (glyphIdx == info.length) { + glyphIdx--; + } + + int charIdx = getGlyphVector().getGlyphCharIndex(glyphIdx); + + return (leading) ^ ((info.level & 0x1) == 0x1)? + TextHitInfo.leading(charIdx + info.start) : + TextHitInfo.trailing(charIdx + info.start); + } + + /** + * Collects GlyphJustificationInfo objects from the glyph vector + * @return array of all GlyphJustificationInfo objects + */ + private GlyphJustificationInfo[] getGlyphJustificationInfos() { + if (gjis == null) { + GlyphVector gv = getGlyphVector(); + int nGlyphs = gv.getNumGlyphs(); + int charIndicies[] = gv.getGlyphCharIndices(0, nGlyphs, null); + gjis = new GlyphJustificationInfo[nGlyphs]; + + // Patch: temporary patch, getGlyphJustificationInfo is not implemented + float fontSize = info.font.getSize2D(); + GlyphJustificationInfo defaultInfo = + new GlyphJustificationInfo( + 0, // weight + false, GlyphJustificationInfo.PRIORITY_NONE, 0, 0, // grow + false, GlyphJustificationInfo.PRIORITY_NONE, 0, 0); // shrink + GlyphJustificationInfo spaceInfo = new GlyphJustificationInfo( + fontSize, // weight + true, GlyphJustificationInfo.PRIORITY_WHITESPACE, 0, fontSize, // grow + true, GlyphJustificationInfo.PRIORITY_WHITESPACE, 0, fontSize); // shrink + + //////// + // Temporary patch, getGlyphJustificationInfo is not implemented + for (int i = 0; i < nGlyphs; i++) { + //gjis[i] = getGlyphVector().getGlyphJustificationInfo(i); + + char c = info.text[charIndicies[i] + info.start]; + if (Character.isWhitespace(c)) { + gjis[i] = spaceInfo; + } else { + gjis[i] = defaultInfo; + } + // End patch + } + } + + return gjis; + } + + /** + * Collects justification information into JustificationInfo object + * @param jInfo - JustificationInfo object + */ + @Override + void updateJustificationInfo(TextRunBreaker.JustificationInfo jInfo) { + int lastChar = Math.min(jInfo.lastIdx, info.end) - info.start; + boolean haveFirst = info.start <= jInfo.firstIdx; + boolean haveLast = info.end >= (jInfo.lastIdx + 1); + + int prevGlyphIdx = -1; + int currGlyphIdx; + + if (jInfo.grow) { // Check how much we can grow/shrink on current priority level + for (int i=0; i 0 ? jInfos[lastPriority] : null; + + boolean haveFirst = info.start <= firstInfo.firstIdx; + boolean haveLast = info.end >= (firstInfo.lastIdx + 1); + + // Here we suppose that GLYPHS are ordered LEFT TO RIGHT + int firstGlyph = haveFirst ? + getChar2Glyph()[firstInfo.firstIdx - info.start] : + getChar2Glyph()[0]; + + int lastGlyph = haveLast ? + getChar2Glyph()[firstInfo.lastIdx - info.start] : + getChar2Glyph()[info.length - 1]; + if (haveLast) { + lastGlyph--; + } + + TextRunBreaker.JustificationInfo currInfo; + float glyphOffset = 0; + float positionIncrement = 0; + float sideIncrement = 0; + + if (haveFirst) { // Don't add padding before first char + GlyphJustificationInfo gji = getGlyphJustificationInfos()[firstGlyph]; + currInfo = jInfos[gji.growPriority]; + if (currInfo != null) { + if (currInfo.useLimits) { + if (currInfo.absorb) { + glyphOffset += gji.weight * currInfo.absorbedGapPerUnit; + } else if ( + lastInfo != null && + lastInfo.priority == currInfo.priority + ) { + glyphOffset += gji.weight * lastInfo.absorbedGapPerUnit; + } + glyphOffset += + firstInfo.grow ? + gji.growRightLimit : + -gji.shrinkRightLimit; + } else { + glyphOffset += gji.weight * currInfo.gapPerUnit; + } + } + + firstGlyph++; + } + + if (firstInfo.grow) { + for (int i=firstGlyph; i<=lastGlyph; i++) { + GlyphJustificationInfo gji = getGlyphJustificationInfos()[i]; + currInfo = jInfos[gji.growPriority]; + if (currInfo == null) { + // We still have to increment glyph position + Point2D glyphPos = getGlyphVector().getGlyphPosition(i); + glyphPos.setLocation(glyphPos.getX() + glyphOffset, glyphPos.getY()); + getGlyphVector().setGlyphPosition(i, glyphPos); + + continue; + } + + if (currInfo.useLimits) { + glyphOffset += gji.growLeftLimit; + if (currInfo.absorb) { + sideIncrement = gji.weight * currInfo.absorbedGapPerUnit; + glyphOffset += sideIncrement; + positionIncrement = glyphOffset; + glyphOffset += sideIncrement; + } else if (lastInfo != null && lastInfo.priority == currInfo.priority) { + sideIncrement = gji.weight * lastInfo.absorbedGapPerUnit; + glyphOffset += sideIncrement; + positionIncrement = glyphOffset; + glyphOffset += sideIncrement; + } else { + positionIncrement = glyphOffset; + } + glyphOffset += gji.growRightLimit; + } else { + sideIncrement = gji.weight * currInfo.gapPerUnit; + glyphOffset += sideIncrement; + positionIncrement = glyphOffset; + glyphOffset += sideIncrement; + } + + Point2D glyphPos = getGlyphVector().getGlyphPosition(i); + glyphPos.setLocation(glyphPos.getX() + positionIncrement, glyphPos.getY()); + getGlyphVector().setGlyphPosition(i, glyphPos); + } + } else { + for (int i=firstGlyph; i<=lastGlyph; i++) { + GlyphJustificationInfo gji = getGlyphJustificationInfos()[i]; + currInfo = jInfos[gji.shrinkPriority]; + if (currInfo == null) { + // We still have to increment glyph position + Point2D glyphPos = getGlyphVector().getGlyphPosition(i); + glyphPos.setLocation(glyphPos.getX() + glyphOffset, glyphPos.getY()); + getGlyphVector().setGlyphPosition(i, glyphPos); + + continue; + } + + if (currInfo.useLimits) { + glyphOffset -= gji.shrinkLeftLimit; + if (currInfo.absorb) { + sideIncrement = gji.weight * currInfo.absorbedGapPerUnit; + glyphOffset += sideIncrement; + positionIncrement = glyphOffset; + glyphOffset += sideIncrement; + } else if (lastInfo != null && lastInfo.priority == currInfo.priority) { + sideIncrement = gji.weight * lastInfo.absorbedGapPerUnit; + glyphOffset += sideIncrement; + positionIncrement = glyphOffset; + glyphOffset += sideIncrement; + } else { + positionIncrement = glyphOffset; + } + glyphOffset -= gji.shrinkRightLimit; + } else { + sideIncrement = gji.weight * currInfo.gapPerUnit; + glyphOffset += sideIncrement; + positionIncrement = glyphOffset; + glyphOffset += sideIncrement; + } + + Point2D glyphPos = getGlyphVector().getGlyphPosition(i); + glyphPos.setLocation(glyphPos.getX() + positionIncrement, glyphPos.getY()); + getGlyphVector().setGlyphPosition(i, glyphPos); + } + } + + + if (haveLast) { // Don't add padding after last char + lastGlyph++; + + GlyphJustificationInfo gji = getGlyphJustificationInfos()[lastGlyph]; + currInfo = jInfos[gji.growPriority]; + + if (currInfo != null) { + if (currInfo.useLimits) { + glyphOffset += firstInfo.grow ? gji.growLeftLimit : -gji.shrinkLeftLimit; + if (currInfo.absorb) { + glyphOffset += gji.weight * currInfo.absorbedGapPerUnit; + } else if (lastInfo != null && lastInfo.priority == currInfo.priority) { + glyphOffset += gji.weight * lastInfo.absorbedGapPerUnit; + } + } else { + glyphOffset += gji.weight * currInfo.gapPerUnit; + } + } + + // Ajust positions of all glyphs after last glyph + for (int i=lastGlyph; i length) { + return length + this.start; + } + return charOffset + start + this.start; + } + + @Override + int getStart() { + return start; + } + + @Override + int getEnd() { + return start + length; + } + + @Override + int getLength() { + return length; + } + + @Override + Shape getCharsBlackBoxBounds(int start, int limit) { + start -= this.start; + limit -= this.start; + + if (limit > length) { + limit = length; + } + + Rectangle2D charBounds = ga.getBounds(); + charBounds.setRect( + charBounds.getX() + ga.getAdvance() * start + x, + charBounds.getY() + y, + charBounds.getWidth() + ga.getAdvance() * (limit - start), + charBounds.getHeight() + ); + + return charBounds; + } + + @Override + float getCharPosition(int index) { + index -= start; + if (index > length) { + index = length; + } + + return ga.getAdvance() * index + x; + } + + @Override + float getCharAdvance(int index) { + return ga.getAdvance(); + } + + @Override + Shape getOutline() { + AffineTransform t = AffineTransform.getTranslateInstance(x, y); + return t.createTransformedShape( + TextDecorator.extendOutline(this, getVisualBounds(), decoration) + ); + } + + @Override + boolean charHasZeroAdvance(int index) { + return false; + } + + @Override + TextHitInfo hitTest(float hitX, float hitY) { + hitX -= x; + + float tmp = hitX / ga.getAdvance(); + int hitIndex = Math.round(tmp); + + if (tmp > hitIndex) { + return TextHitInfo.leading(hitIndex + this.start); + } + return TextHitInfo.trailing(hitIndex + this.start); + } + + @Override + void updateJustificationInfo(TextRunBreaker.JustificationInfo jInfo) { + // Do nothing + } + + @Override + float doJustification(TextRunBreaker.JustificationInfo jInfos[]) { + // Do nothing + return 0; + } + } +} diff --git a/awt/org/apache/harmony/awt/gl/image/BufferedImageGraphics2D.java b/awt/org/apache/harmony/awt/gl/image/BufferedImageGraphics2D.java new file mode 100644 index 0000000..f1d64fb --- /dev/null +++ b/awt/org/apache/harmony/awt/gl/image/BufferedImageGraphics2D.java @@ -0,0 +1,79 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Alexey A. Petrenko + * @version $Revision$ + */ +package org.apache.harmony.awt.gl.image; + +import java.awt.Graphics; +import java.awt.GraphicsConfiguration; +import java.awt.Rectangle; +import java.awt.image.BufferedImage; +import java.awt.image.ColorModel; +import java.awt.image.WritableRaster; + +import org.apache.harmony.awt.gl.CommonGraphics2D; +import org.apache.harmony.awt.gl.Surface; +import org.apache.harmony.awt.gl.render.JavaBlitter; +import org.apache.harmony.awt.gl.render.NativeImageBlitter; + +/** + * BufferedImageGraphics2D is implementation of CommonGraphics2D for + * drawing on buffered images. + */ +public class BufferedImageGraphics2D extends CommonGraphics2D { + private BufferedImage bi = null; + private Rectangle bounds = null; + + public BufferedImageGraphics2D(BufferedImage bi) { + super(); + this.bi = bi; + this.bounds = new Rectangle(0, 0, bi.getWidth(), bi.getHeight()); + clip(bounds); + dstSurf = Surface.getImageSurface(bi); + if(dstSurf.isNativeDrawable()){ + blitter = NativeImageBlitter.getInstance(); + }else{ + blitter = JavaBlitter.getInstance(); + } + } + + @Override + public void copyArea(int x, int y, int width, int height, int dx, int dy) { + } + + @Override + public Graphics create() { + BufferedImageGraphics2D res = new BufferedImageGraphics2D(bi); + copyInternalFields(res); + return res; + } + + @Override + public GraphicsConfiguration getDeviceConfiguration() { + return null; + } + + public ColorModel getColorModel() { + return bi.getColorModel(); + } + + public WritableRaster getWritableRaster() { + return bi.getRaster(); + } +} \ No newline at end of file diff --git a/awt/org/apache/harmony/awt/gl/image/BufferedImageSource.java b/awt/org/apache/harmony/awt/gl/image/BufferedImageSource.java new file mode 100644 index 0000000..0fe25a2 --- /dev/null +++ b/awt/org/apache/harmony/awt/gl/image/BufferedImageSource.java @@ -0,0 +1,136 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Igor V. Stolyarov + * @version $Revision$ + */ + +package org.apache.harmony.awt.gl.image; + +import java.awt.image.BufferedImage; +import java.awt.image.ColorModel; +import java.awt.image.ComponentColorModel; +import java.awt.image.DataBuffer; +import java.awt.image.DataBufferByte; +import java.awt.image.DataBufferInt; +import java.awt.image.DirectColorModel; +import java.awt.image.ImageConsumer; +import java.awt.image.ImageProducer; +import java.awt.image.IndexColorModel; +import java.awt.image.WritableRaster; +import java.util.Hashtable; + +public class BufferedImageSource implements ImageProducer { + + private Hashtable properties; + private ColorModel cm; + private WritableRaster raster; + private int width; + private int height; + + private ImageConsumer ic; + + public BufferedImageSource(BufferedImage image, Hashtable properties){ + if(properties == null) { + this.properties = new Hashtable(); + } else { + this.properties = properties; + } + + width = image.getWidth(); + height = image.getHeight(); + cm = image.getColorModel(); + raster = image.getRaster(); + } + + public BufferedImageSource(BufferedImage image){ + this(image, null); + } + + public boolean isConsumer(ImageConsumer ic) { + return (this.ic == ic); + } + + public void startProduction(ImageConsumer ic) { + addConsumer(ic); + } + + public void requestTopDownLeftRightResend(ImageConsumer ic) { + } + + public void removeConsumer(ImageConsumer ic) { + if (this.ic == ic) { + this.ic = null; + } + } + + public void addConsumer(ImageConsumer ic) { + this.ic = ic; + startProduction(); + } + + private void startProduction(){ + try { + ic.setDimensions(width, height); + ic.setProperties(properties); + ic.setColorModel(cm); + ic.setHints(ImageConsumer.TOPDOWNLEFTRIGHT | + ImageConsumer.COMPLETESCANLINES | + ImageConsumer.SINGLEFRAME | + ImageConsumer.SINGLEPASS); + if(cm instanceof IndexColorModel && + raster.getTransferType() == DataBuffer.TYPE_BYTE || + cm instanceof ComponentColorModel && + raster.getTransferType() == DataBuffer.TYPE_BYTE && + raster.getNumDataElements() == 1){ + DataBufferByte dbb = (DataBufferByte) raster.getDataBuffer(); + byte data[] = dbb.getData(); + int off = dbb.getOffset(); + ic.setPixels(0, 0, width, height, cm, data, off, width); + }else if(cm instanceof DirectColorModel && + raster.getTransferType() == DataBuffer.TYPE_INT){ + DataBufferInt dbi = (DataBufferInt) raster.getDataBuffer(); + int data[] = dbi.getData(); + int off = dbi.getOffset(); + ic.setPixels(0, 0, width, height, cm, data, off, width); + }else if(cm instanceof DirectColorModel && + raster.getTransferType() == DataBuffer.TYPE_BYTE){ + DataBufferByte dbb = (DataBufferByte) raster.getDataBuffer(); + byte data[] = dbb.getData(); + int off = dbb.getOffset(); + ic.setPixels(0, 0, width, height, cm, data, off, width); + }else{ + ColorModel rgbCM = ColorModel.getRGBdefault(); + int pixels[] = new int[width]; + Object pix = null; + for(int y = 0; y < height; y++){ + for(int x = 0 ; x < width; x++){ + pix = raster.getDataElements(x, y, pix); + pixels[x] = cm.getRGB(pix); + } + ic.setPixels(0, y, width, 1, rgbCM, pixels, 0, width); + } + } + ic.imageComplete(ImageConsumer.STATICIMAGEDONE); + }catch (NullPointerException e){ + if (ic != null) { + ic.imageComplete(ImageConsumer.IMAGEERROR); + } + } + } + +} diff --git a/awt/org/apache/harmony/awt/gl/image/ByteArrayDecodingImageSource.java b/awt/org/apache/harmony/awt/gl/image/ByteArrayDecodingImageSource.java new file mode 100644 index 0000000..cc6d7cf --- /dev/null +++ b/awt/org/apache/harmony/awt/gl/image/ByteArrayDecodingImageSource.java @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Igor V. Stolyarov + * @version $Revision$ + */ +/* + * Created on 10.02.2005 + * + */ +package org.apache.harmony.awt.gl.image; + +import java.io.BufferedInputStream; +import java.io.ByteArrayInputStream; +import java.io.InputStream; + +public class ByteArrayDecodingImageSource extends DecodingImageSource { + + byte imagedata[]; + int imageoffset; + int imagelength; + + public ByteArrayDecodingImageSource(byte imagedata[], int imageoffset, + int imagelength){ + this.imagedata = imagedata; + this.imageoffset = imageoffset; + this.imagelength = imagelength; + } + + public ByteArrayDecodingImageSource(byte imagedata[]){ + this(imagedata, 0, imagedata.length); + } + + @Override + protected boolean checkConnection() { + return true; + } + + @Override + protected InputStream getInputStream() { + // BEGIN android-modified + // TODO: Why does a ByteArrayInputStream need to be buffered at all? + return new BufferedInputStream(new ByteArrayInputStream(imagedata, + imageoffset, imagelength), 1024); + // END android-modified + } + +} diff --git a/awt/org/apache/harmony/awt/gl/image/DataBufferListener.java b/awt/org/apache/harmony/awt/gl/image/DataBufferListener.java new file mode 100644 index 0000000..8793050 --- /dev/null +++ b/awt/org/apache/harmony/awt/gl/image/DataBufferListener.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Igor V. Stolyarov + * @version $Revision$ + * Created on 13.03.2006 + * + */ +package org.apache.harmony.awt.gl.image; + +public interface DataBufferListener { + + void dataChanged(); + void dataTaken(); + void dataReleased(); + +} diff --git a/awt/org/apache/harmony/awt/gl/image/DecodingImageSource.java b/awt/org/apache/harmony/awt/gl/image/DecodingImageSource.java new file mode 100644 index 0000000..958d691 --- /dev/null +++ b/awt/org/apache/harmony/awt/gl/image/DecodingImageSource.java @@ -0,0 +1,261 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Oleg V. Khaschansky + * @version $Revision$ + */ +/* + * Created on 18.01.2005 + */ +package org.apache.harmony.awt.gl.image; + +import java.awt.image.ImageConsumer; +import java.awt.image.ImageProducer; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * This is an abstract class that encapsulates a main part of ImageProducer functionality + * for the images being decoded by the native decoders, like PNG, JPEG and GIF. + * It helps to integrate image decoders into producer/consumer model. It provides + * functionality for working with several decoder instances and several image consumers + * simultaneously. + */ +public abstract class DecodingImageSource implements ImageProducer { + List consumers = new ArrayList(5); + List decoders = new ArrayList(5); + boolean loading; + + ImageDecoder decoder; + + protected abstract boolean checkConnection(); + + protected abstract InputStream getInputStream(); + + public synchronized void addConsumer(ImageConsumer ic) { + if (!checkConnection()) { // No permission for this consumer + ic.imageComplete(ImageConsumer.IMAGEERROR); + return; + } + + ImageConsumer cons = findConsumer(consumers, ic); + + if (cons == null) { // Try to look in the decoders + ImageDecoder d = null; + + // Check for all existing decoders + for (Iterator i = decoders.iterator(); i.hasNext();) { + d = i.next(); + cons = findConsumer(d.consumers, ic); + if (cons != null) { + break; + } + } + } + + if (cons == null) { // Not found, add this consumer + consumers.add(ic); + } + } + + /** + * This method stops sending data to the given consumer + * @param ic - consumer + */ + private void abortConsumer(ImageConsumer ic) { + ic.imageComplete(ImageConsumer.IMAGEERROR); + consumers.remove(ic); + } + + /** + * This method stops sending data to the list of consumers. + * @param consumersList - list of consumers + */ + private void abortAllConsumers(List consumersList) { + for (ImageConsumer imageConsumer : consumersList) { + abortConsumer(imageConsumer); + } + } + + public synchronized void removeConsumer(ImageConsumer ic) { + ImageDecoder d = null; + + // Remove in all existing decoders + for (Iterator i = decoders.iterator(); i.hasNext();) { + d = i.next(); + removeConsumer(d.consumers, ic); + if (d.consumers.size() <= 0) { + d.terminate(); + } + } + + // Remove in the current queue of consumers + removeConsumer(consumers, ic); + } + + /** + * Static implementation of removeConsumer method + * @param consumersList - list of consumers + * @param ic - consumer to be removed + */ + private static void removeConsumer(List consumersList, ImageConsumer ic) { + ImageConsumer cons = null; + + for (Iterator i = consumersList.iterator(); i.hasNext();) { + cons = i.next(); + if (cons.equals(ic)) { + i.remove(); + } + } + } + + public void requestTopDownLeftRightResend(ImageConsumer consumer) { + // Do nothing + } + + public synchronized void startProduction(ImageConsumer ic) { + if (ic != null) { + addConsumer(ic); + } + + if (!loading && consumers.size() > 0) { + ImageLoader.addImageSource(this); + loading = true; + } + } + + public synchronized boolean isConsumer(ImageConsumer ic) { + ImageDecoder d = null; + + // Check for all existing decoders + for (Iterator i = decoders.iterator(); i.hasNext();) { + d = i.next(); + if (findConsumer(d.consumers, ic) != null) { + return true; + } + } + + // Check current queue of consumers + return findConsumer(consumers, ic) != null; + } + + /** + * Checks if the consumer is in the list and returns it it is there + * @param consumersList - list of consumers + * @param ic - consumer + * @return consumer if found, null otherwise + */ + private static ImageConsumer findConsumer(List consumersList, ImageConsumer ic) { + ImageConsumer res = null; + + for (Iterator i = consumersList.iterator(); i.hasNext();) { + res = i.next(); + if (res.equals(ic)) { + return res; + } + } + + return null; + } + + /** + * Use this method to finish decoding or lock the list of consumers + * for a particular decoder + * @param d - decoder + */ + synchronized void lockDecoder(ImageDecoder d) { + if (d == decoder) { + decoder = null; + startProduction(null); + } + } + + /** + * Tries to find an appropriate decoder for the input stream and adds it + * to the list of decoders + * @return created decoder + */ + private ImageDecoder createDecoder() { + InputStream is = getInputStream(); + + ImageDecoder decoder; + + if (is == null) { + decoder = null; + } else { + decoder = ImageDecoder.createDecoder(this, is); + } + + if (decoder != null) { + synchronized (this) { + decoders.add(decoder); + this.decoder = decoder; + loading = false; + consumers = new ArrayList(5); // Reset queue + } + + return decoder; + } + // We were not able to find appropriate decoder + List cs; + synchronized (this) { + cs = consumers; + consumers = new ArrayList(5); + loading = false; + } + abortAllConsumers(cs); + + return null; + } + + /** + * Stop the given decoder and remove it from the list + * @param dr - decoder + */ + private synchronized void removeDecoder(ImageDecoder dr) { + lockDecoder(dr); + decoders.remove(dr); + } + + /** + * This method serves as an entry point. + * It starts the decoder and loads the image data. + */ + public void load() { + synchronized (this) { + if (consumers.size() == 0) { + loading = false; + return; + } + } + + ImageDecoder d = createDecoder(); + if (d != null) { + try { + decoder.decodeImage(); + } catch (IOException e) { + e.printStackTrace(); + } finally { + removeDecoder(d); + abortAllConsumers(d.consumers); + } + } + } +} diff --git a/awt/org/apache/harmony/awt/gl/image/FileDecodingImageSource.java b/awt/org/apache/harmony/awt/gl/image/FileDecodingImageSource.java new file mode 100644 index 0000000..54d4664 --- /dev/null +++ b/awt/org/apache/harmony/awt/gl/image/FileDecodingImageSource.java @@ -0,0 +1,68 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Oleg V. Khaschansky + * @version $Revision$ + */ +/* + * Created on 20.01.2005 + */ +package org.apache.harmony.awt.gl.image; + +import java.io.BufferedInputStream; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.InputStream; + +public class FileDecodingImageSource extends DecodingImageSource { + String filename; + + public FileDecodingImageSource(String file) { + SecurityManager security = System.getSecurityManager(); + if (security != null) { + security.checkRead(file); + } + + filename = file; + } + + @Override +protected boolean checkConnection() { + SecurityManager security = System.getSecurityManager(); + if (security != null) { + try { + security.checkRead(filename); + } catch (SecurityException e) { + return false; + } + } + + return true; + } + + @Override +protected InputStream getInputStream() { + try { + // BEGIN android-modified + return new BufferedInputStream(new FileInputStream(filename), 8192); + // END android-modified + } catch (FileNotFoundException e) { + return null; + } + } + +} diff --git a/awt/org/apache/harmony/awt/gl/image/GifDecoder.java b/awt/org/apache/harmony/awt/gl/image/GifDecoder.java new file mode 100644 index 0000000..7ecb15b --- /dev/null +++ b/awt/org/apache/harmony/awt/gl/image/GifDecoder.java @@ -0,0 +1,692 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Oleg V. Khaschansky + * @version $Revision$ + */ +/* +* Created on 27.01.2005 +*/ +package org.apache.harmony.awt.gl.image; + +import java.awt.image.ColorModel; +import java.awt.image.ImageConsumer; +import java.awt.image.IndexColorModel; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Hashtable; +import java.util.List; + +public class GifDecoder extends ImageDecoder { + // initializes proper field IDs + private static native void initIDs(); + + static { + System.loadLibrary("gl"); //$NON-NLS-1$ + initIDs(); + } + + // ImageConsumer hints: common + private static final int baseHints = + ImageConsumer.SINGLEPASS | ImageConsumer.COMPLETESCANLINES | + ImageConsumer.SINGLEFRAME; + // ImageConsumer hints: interlaced + private static final int interlacedHints = + baseHints | ImageConsumer.RANDOMPIXELORDER; + + // Impossible color value - no translucent pixels allowed + static final int IMPOSSIBLE_VALUE = 0x0FFFFFFF; + + // I/O buffer + private static final int BUFFER_SIZE = 1024; + private byte buffer[] = new byte[BUFFER_SIZE]; + + GifDataStream gifDataStream = new GifDataStream(); + GifGraphicBlock currBlock; + + // Pointer to native structure which store decoding state + // between subsequent decoding/IO-suspension cycles + private long hNativeDecoder; // NULL initially + + // Number of bytes eaten by the native decoder + private int bytesConsumed; + + private boolean consumersPrepared; + private Hashtable properties = new Hashtable(); + + // Could be set up by java code or native method when + // transparent pixel index changes or local color table encountered + private boolean forceRGB; + + private byte screenBuffer[]; + private int screenRGBBuffer[]; + + public GifDecoder(DecodingImageSource src, InputStream is) { + super(src, is); + } + + private static native int[] toRGB(byte imageData[], byte colormap[], int transparentColor); + + private static native void releaseNativeDecoder(long hDecoder); + + private native int decode( + byte input[], + int bytesInBuffer, + long hDecoder, + GifDataStream dataStream, + GifGraphicBlock currBlock + ); + + private int[] getScreenRGBBuffer() { + if (screenRGBBuffer == null) { + if (screenBuffer != null) { + int transparentColor = + gifDataStream.logicalScreen.globalColorTable.cm.getTransparentPixel(); + transparentColor = transparentColor > 0 ? transparentColor : IMPOSSIBLE_VALUE; + screenRGBBuffer = + toRGB( + screenBuffer, + gifDataStream.logicalScreen.globalColorTable.colors, + transparentColor + ); + } else { + int size = gifDataStream.logicalScreen.logicalScreenHeight * + gifDataStream.logicalScreen.logicalScreenWidth; + screenRGBBuffer = new int[size]; + } + } + + return screenRGBBuffer; + } + + private void prepareConsumers() { + GifLogicalScreen gls = gifDataStream.logicalScreen; + setDimensions(gls.logicalScreenWidth, + gls.logicalScreenHeight); + setProperties(properties); + + currBlock = gifDataStream.graphicBlocks.get(0); + if (forceRGB) { + setColorModel(ColorModel.getRGBdefault()); + } else { + setColorModel(gls.globalColorTable.getColorModel(currBlock.transparentColor)); + } + + // Fill screen buffer with the background or transparent color + if (forceRGB) { + int fillColor = 0xFF000000; + if (gls.backgroundColor != IMPOSSIBLE_VALUE) { + fillColor = gls.backgroundColor; + } + + Arrays.fill(getScreenRGBBuffer(), fillColor); + } else { + int fillColor = 0; + + if (gls.backgroundColor != IMPOSSIBLE_VALUE) { + fillColor = gls.backgroundColor; + } else { + fillColor = gls.globalColorTable.cm.getTransparentPixel(); + } + + screenBuffer = new byte[gls.logicalScreenHeight*gls.logicalScreenWidth]; + Arrays.fill(screenBuffer, (byte) fillColor); + } + + setHints(interlacedHints); // XXX - always random pixel order + } + + @Override + public void decodeImage() throws IOException { + try { + int bytesRead = 0; + int needBytes, offset, bytesInBuffer = 0; + boolean eosReached = false; + GifGraphicBlock blockToDispose = null; + + // Create new graphic block + if (currBlock == null) { + currBlock = new GifGraphicBlock(); + gifDataStream.graphicBlocks.add(currBlock); + } + + // Read from the input stream + for (;;) { + needBytes = BUFFER_SIZE - bytesInBuffer; + offset = bytesInBuffer; + + bytesRead = inputStream.read(buffer, offset, needBytes); + + if (bytesRead < 0) { + eosReached = true; + bytesRead = 0; + } // Don't break, maybe something left in buffer + + // Keep track on how much bytes left in buffer + bytesInBuffer += bytesRead; + + // Here we pass number of new bytes read from the input stream (bytesRead) + // since native decoder uses java buffer and doesn't have its own + // buffer. So it adds this number to the number of bytes left + // in buffer from the previous call. + int numLines = decode( + buffer, + bytesRead, + hNativeDecoder, + gifDataStream, + currBlock); + + // Keep track on how much bytes left in buffer + bytesInBuffer -= bytesConsumed; + + if ( + !consumersPrepared && + gifDataStream.logicalScreen.completed && + gifDataStream.logicalScreen.globalColorTable.completed && + (currBlock.imageData != null || // Have transparent pixel filled + currBlock.rgbImageData != null) + ) { + prepareConsumers(); + consumersPrepared = true; + } + + if (bytesConsumed < 0) { + break; // Error exit + } + + if (currBlock != null) { + if (numLines != 0) { + // Dispose previous image only before showing next + if (blockToDispose != null) { + blockToDispose.dispose(); + blockToDispose = null; + } + + currBlock.sendNewData(this, numLines); + } + + if (currBlock.completed && hNativeDecoder != 0) { + blockToDispose = currBlock; // Dispose only before showing new pixels + currBlock = new GifGraphicBlock(); + gifDataStream.graphicBlocks.add(currBlock); + } + } + + if (hNativeDecoder == 0) { + break; + } + + if (eosReached && numLines == 0) { // Maybe image is truncated... + releaseNativeDecoder(hNativeDecoder); + break; + } + } + } finally { + closeStream(); + } + + // Here all animation goes + // Repeat image loopCount-1 times or infinitely if loopCount = 0 + if (gifDataStream.loopCount != 1) { + if (currBlock.completed == false) { + gifDataStream.graphicBlocks.remove(currBlock); + } + + int numFrames = gifDataStream.graphicBlocks.size(); + // At first last block will be disposed + GifGraphicBlock gb = + gifDataStream.graphicBlocks.get(numFrames-1); + + ImageLoader.beginAnimation(); + + while (gifDataStream.loopCount != 1) { + if (gifDataStream.loopCount != 0) { + gifDataStream.loopCount--; + } + + // Show all frames + for (int i=0; i graphicBlocks = new ArrayList(10); // Of GifGraphicBlocks + + // Comments from the image + String comments[]; + } + + class GifLogicalScreen { + // Indicates that reading of this block accomplished + boolean completed = false; + + int logicalScreenWidth; + int logicalScreenHeight; + + int backgroundColor = IMPOSSIBLE_VALUE; + + GifColorTable globalColorTable = new GifColorTable(); + } + + class GifGraphicBlock { + // Indicates that reading of this block accomplished + boolean completed = false; + + final static int DISPOSAL_NONE = 0; + final static int DISPOSAL_NODISPOSAL = 1; + final static int DISPOSAL_BACKGROUND = 2; + final static int DISPOSAL_RESTORE = 3; + + int disposalMethod; + int delayTime; // Multiplied by 10 already + int transparentColor = IMPOSSIBLE_VALUE; + + int imageLeft; + int imageTop; + int imageWidth; + int imageHeight; + + // Auxilliary variables to minimize computations + int imageRight; + int imageBottom; + + boolean interlace; + + // Don't need local color table - if it is specified + // image data are converted to RGB in the native code + + byte imageData[] = null; + int rgbImageData[] = null; + + private int currY = 0; // Current output scanline + + int[] getRgbImageData() { + if (rgbImageData == null) { + rgbImageData = + toRGB( + imageData, + gifDataStream.logicalScreen.globalColorTable.colors, + transparentColor + ); + if (transparentColor != IMPOSSIBLE_VALUE) { + transparentColor = + gifDataStream.logicalScreen.globalColorTable.cm.getRGB(transparentColor); + transparentColor &= 0x00FFFFFF; + } + } + return rgbImageData; + } + + private void replaceTransparentPixels(int numLines) { + List graphicBlocks = gifDataStream.graphicBlocks; + int prevBlockIndex = graphicBlocks.indexOf(this) - 1; + + if (prevBlockIndex >= 0) { + int maxY = currY + numLines + imageTop; + int offset = currY * imageWidth; + + // Update right and bottom coordinates + imageRight = imageLeft + imageWidth; + imageBottom = imageTop + imageHeight; + + int globalWidth = gifDataStream.logicalScreen.logicalScreenWidth; + int pixelValue, imageOffset; + int rgbData[] = forceRGB ? getRgbImageData() : null; + + for (int y = currY + imageTop; y < maxY; y++) { + imageOffset = globalWidth * y + imageLeft; + for (int x = imageLeft; x < imageRight; x++) { + pixelValue = forceRGB ? + rgbData[offset] : + imageData[offset] & 0xFF; + if (pixelValue == transparentColor) { + if (forceRGB) { + pixelValue = getScreenRGBBuffer() [imageOffset]; + rgbData[offset] = pixelValue; + } else { + pixelValue = screenBuffer [imageOffset]; + imageData[offset] = (byte) pixelValue; + } + } + offset++; + imageOffset++; + } // for + } // for + + } // if (prevBlockIndex >= 0) + } + + public void sendNewData(GifDecoder decoder, int numLines) { + // Get values for transparent pixels + // from the perevious frames + if (transparentColor != IMPOSSIBLE_VALUE) { + replaceTransparentPixels(numLines); + } + + if (forceRGB) { + decoder.setPixels( + imageLeft, + imageTop + currY, + imageWidth, + numLines, + ColorModel.getRGBdefault(), + getRgbImageData(), + currY*imageWidth, + imageWidth + ); + } else { + decoder.setPixels( + imageLeft, + imageTop + currY, + imageWidth, + numLines, + null, + imageData, + currY*imageWidth, + imageWidth + ); + } + + currY += numLines; + } + + public void dispose() { + imageComplete(ImageConsumer.SINGLEFRAMEDONE); + + // Show current frame until delayInterval will not elapse + if (delayTime > 0) { + try { + Thread.sleep(delayTime); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } else { + Thread.yield(); // Allow consumers to consume data + } + + // Don't dispose if image is outside of the visible area + if (imageLeft > gifDataStream.logicalScreen.logicalScreenWidth || + imageTop > gifDataStream.logicalScreen.logicalScreenHeight) { + disposalMethod = DISPOSAL_NONE; + } + + switch(disposalMethod) { + case DISPOSAL_BACKGROUND: { + if (forceRGB) { + getRgbImageData(); // Ensure that transparentColor is RGB, not index + + int data[] = new int[imageWidth*imageHeight]; + + // Compatibility: Fill with transparent color if we have one + if (transparentColor != IMPOSSIBLE_VALUE) { + Arrays.fill( + data, + transparentColor + ); + } else { + Arrays.fill( + data, + gifDataStream.logicalScreen.backgroundColor + ); + } + + setPixels( + imageLeft, + imageTop, + imageWidth, + imageHeight, + ColorModel.getRGBdefault(), + data, + 0, + imageWidth + ); + + sendToScreenBuffer(data); + } else { + byte data[] = new byte[imageWidth*imageHeight]; + + // Compatibility: Fill with transparent color if we have one + if (transparentColor != IMPOSSIBLE_VALUE) { + Arrays.fill( + data, + (byte) transparentColor + ); + } else { + Arrays.fill( + data, + (byte) gifDataStream.logicalScreen.backgroundColor + ); + } + + setPixels( + imageLeft, + imageTop, + imageWidth, + imageHeight, + null, + data, + 0, + imageWidth + ); + + sendToScreenBuffer(data); + } + break; + } + case DISPOSAL_RESTORE: { + screenBufferToScreen(); + break; + } + case DISPOSAL_NONE: + case DISPOSAL_NODISPOSAL: + default: { + // Copy transmitted data to the screen buffer + Object data = forceRGB ? (Object) getRgbImageData() : imageData; + sendToScreenBuffer(data); + break; + } + } + } + + private void sendToScreenBuffer(Object data) { + int dataInt[]; + byte dataByte[]; + + int width = gifDataStream.logicalScreen.logicalScreenWidth; + + + if (forceRGB) { + dataInt = (int[]) data; + + if (imageWidth == width) { + System.arraycopy(dataInt, + 0, + getScreenRGBBuffer(), + imageLeft + imageTop*width, + dataInt.length + ); + } else { // Each scanline + copyScanlines(dataInt, getScreenRGBBuffer(), width); + } + } else { + dataByte = (byte[]) data; + + if (imageWidth == width) { + System.arraycopy(dataByte, + 0, + screenBuffer, + imageLeft + imageTop*width, + dataByte.length + ); + } else { // Each scanline + copyScanlines(dataByte, screenBuffer, width); + } + } + } // sendToScreenBuffer + + private void copyScanlines(Object src, Object dst, int width) { + for (int i=0; i 0) { + if (transparentColor == IMPOSSIBLE_VALUE) { + return cm = + new IndexColorModel(8, size, colors, 0, false); + } + + if (transparentColor > size) { + size = transparentColor + 1; + } + return cm = + new IndexColorModel(8, size, colors, 0, false, transparentColor); + } + + return cm = null; // Force default ARGB color model + } + } +} diff --git a/awt/org/apache/harmony/awt/gl/image/ImageDecoder.java b/awt/org/apache/harmony/awt/gl/image/ImageDecoder.java new file mode 100644 index 0000000..d16128e --- /dev/null +++ b/awt/org/apache/harmony/awt/gl/image/ImageDecoder.java @@ -0,0 +1,258 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Oleg V. Khaschansky + * @version $Revision$ + */ +/* + * Created on 18.01.2005 + */ +package org.apache.harmony.awt.gl.image; + +import com.android.internal.awt.AndroidImageDecoder; + +import java.awt.image.ColorModel; +import java.awt.image.ImageConsumer; +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.ConcurrentModificationException; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.List; + + +/** + * This class contains common functionality for all image decoders. + */ +public abstract class ImageDecoder { + + /** Image types */ + public static final int GENERIC_DECODER = 0; + public static final int JPG_DECODER = 1; + public static final int GIF_DECODER = 2; + public static final int PNG_DECODER = 3; + + private static final int MAX_BYTES_IN_SIGNATURE = 8; + + protected List consumers; + protected InputStream inputStream; + protected DecodingImageSource src; + + protected boolean terminated; + + /** + * Chooses appropriate image decoder by looking into input stream and checking + * the image signature. + * @param src - image producer, required for passing data to it from the + * created decoder via callbacks + * @param is - stream + * @return decoder + */ + static ImageDecoder createDecoder(DecodingImageSource src, InputStream is) { + InputStream markable; + + if (!is.markSupported()) { + // BEGIN android-modified + markable = new BufferedInputStream(is, 8192); + // END android-modified + } else { + markable = is; + } + + // Read the signature from the stream and then reset it back + try { + markable.mark(MAX_BYTES_IN_SIGNATURE); + + byte[] signature = new byte[MAX_BYTES_IN_SIGNATURE]; + markable.read(signature, 0, MAX_BYTES_IN_SIGNATURE); + markable.reset(); + + if ((signature[0] & 0xFF) == 0xFF && + (signature[1] & 0xFF) == 0xD8 && + (signature[2] & 0xFF) == 0xFF) { // JPEG + return loadDecoder(PNG_DECODER, src, is); + } else if ((signature[0] & 0xFF) == 0x47 && // G + (signature[1] & 0xFF) == 0x49 && // I + (signature[2] & 0xFF) == 0x46) { // F + return loadDecoder(GIF_DECODER, src, is); + } else if ((signature[0] & 0xFF) == 137 && // PNG signature: 137 80 78 71 13 10 26 10 + (signature[1] & 0xFF) == 80 && + (signature[2] & 0xFF) == 78 && + (signature[3] & 0xFF) == 71 && + (signature[4] & 0xFF) == 13 && + (signature[5] & 0xFF) == 10 && + (signature[6] & 0xFF) == 26 && + (signature[7] & 0xFF) == 10) { + return loadDecoder(JPG_DECODER, src, is); + } + + return loadDecoder(GENERIC_DECODER, src, is); + + } catch (IOException e) { // Silently + } + + return null; + } + + /* + * In the future, we might return different decoders for differen image types. + * But for now, we always return the generic one. + * Also: we could add a factory to load image decoder. + */ + private static ImageDecoder loadDecoder(int type, DecodingImageSource src, + InputStream is) { + return new AndroidImageDecoder(src, is); + } + + protected ImageDecoder(DecodingImageSource _src, InputStream is) { + src = _src; + consumers = src.consumers; + inputStream = is; + } + + public abstract void decodeImage() throws IOException; + + public synchronized void closeStream() { + if (inputStream != null) { + try { + inputStream.close(); + } catch (IOException e) { + } + } + } + + /** + * Stops the decoding by interrupting the current decoding thread. + * Used when all consumers are removed and there's no more need to + * run the decoder. + */ + public void terminate() { + src.lockDecoder(this); + closeStream(); + + AccessController.doPrivileged( + new PrivilegedAction() { + public Void run() { + Thread.currentThread().interrupt(); + return null; + } + } + ); + + terminated = true; + } + + protected void setDimensions(int w, int h) { + if (terminated) { + return; + } + + for (ImageConsumer ic : consumers) { + ic.setDimensions(w, h); + } + } + + protected void setProperties(Hashtable props) { + if (terminated) { + return; + } + + for (ImageConsumer ic : consumers) { + ic.setProperties(props); + } + } + + protected void setColorModel(ColorModel cm) { + if (terminated) { + return; + } + + for (ImageConsumer ic : consumers) { + ic.setColorModel(cm); + } + } + + protected void setHints(int hints) { + if (terminated) { + return; + } + + for (ImageConsumer ic : consumers) { + ic.setHints(hints); + } + } + + protected void setPixels( + int x, int y, + int w, int h, + ColorModel model, + byte pix[], + int off, int scansize + ) { + if (terminated) { + return; + } + + src.lockDecoder(this); + + for (ImageConsumer ic : consumers) { + ic.setPixels(x, y, w, h, model, pix, off, scansize); + } + } + + protected void setPixels( + int x, int y, + int w, int h, + ColorModel model, + int pix[], + int off, int scansize + ) { + if (terminated) { + return; + } + + src.lockDecoder(this); + + for (ImageConsumer ic : consumers) { + ic.setPixels(x, y, w, h, model, pix, off, scansize); + } + } + + protected void imageComplete(int status) { + if (terminated) { + return; + } + + src.lockDecoder(this); + + ImageConsumer ic = null; + + for (Iterator i = consumers.iterator(); i.hasNext();) { + try { + ic = i.next(); + } catch (ConcurrentModificationException e) { + i = consumers.iterator(); + continue; + } + ic.imageComplete(status); + } + } + +} diff --git a/awt/org/apache/harmony/awt/gl/image/ImageLoader.java b/awt/org/apache/harmony/awt/gl/image/ImageLoader.java new file mode 100644 index 0000000..5c7d180 --- /dev/null +++ b/awt/org/apache/harmony/awt/gl/image/ImageLoader.java @@ -0,0 +1,208 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Oleg V. Khaschansky + * @version $Revision$ + */ +/* + * Created on 18.01.2005 + */ +package org.apache.harmony.awt.gl.image; + +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; + +/** + * This class provides functionality for simultaneous loading of + * several images and running animation. + */ +public class ImageLoader extends Thread { + // Contains ImageLoader objects + // and queue of image sources waiting to be loaded + static class ImageLoadersStorage { + private static final int MAX_THREADS = 5; + private static final int TIMEOUT = 4000; + static ImageLoadersStorage instance; + + List queue = new LinkedList(); + List loaders = new ArrayList(MAX_THREADS); + + private int freeLoaders; + + private ImageLoadersStorage() {} + + static ImageLoadersStorage getStorage() { + if (instance == null) { + instance = new ImageLoadersStorage(); + } + + return instance; + } + } + + ImageLoader() { + super(); + setDaemon(true); + } + + /** + * This method creates a new thread which is able to load an image + * or run animation (if the number of existing loader threads does not + * exceed the limit). + */ + private static void createLoader() { + final ImageLoadersStorage storage = ImageLoadersStorage.getStorage(); + + synchronized(storage.loaders) { + if (storage.loaders.size() < ImageLoadersStorage.MAX_THREADS) { + AccessController.doPrivileged( + new PrivilegedAction() { + public Void run() { + ImageLoader loader = new ImageLoader(); + storage.loaders.add(loader); + loader.start(); + return null; + } + }); + } + } + } + + /** + * Adds a new image source to the queue and starts a new loader + * thread if required + * @param imgSrc - image source + */ + public static void addImageSource(DecodingImageSource imgSrc) { + ImageLoadersStorage storage = ImageLoadersStorage.getStorage(); + synchronized(storage.queue) { + if (!storage.queue.contains(imgSrc)) { + storage.queue.add(imgSrc); + } + if (storage.freeLoaders == 0) { + createLoader(); + } + + storage.queue.notify(); + } + } + + /** + * Waits for a new ImageSource until timout expires. + * Loader thread will terminate after returning from this method + * if timeout expired and image source was not picked up from the queue. + * @return image source picked up from the queue or null if timeout expired + */ + private static DecodingImageSource getWaitingImageSource() { + ImageLoadersStorage storage = ImageLoadersStorage.getStorage(); + + synchronized(storage.queue) { + DecodingImageSource isrc = null; + + if (storage.queue.size() == 0) { + try { + storage.freeLoaders++; + storage.queue.wait(ImageLoadersStorage.TIMEOUT); + } catch (InterruptedException e) { + return null; + } finally { + storage.freeLoaders--; + } + } + + if (storage.queue.size() > 0) { + isrc = storage.queue.get(0); + storage.queue.remove(0); + } + + return isrc; + } + } + + /** + * Entry point of the loader thread. Picks up image sources and + * runs decoders for them while there are available image sources in the queue. + * If there are no and timeout expires it terminates. + */ + @Override + public void run() { + ImageLoadersStorage storage = ImageLoadersStorage.getStorage(); + + try { + while (storage.loaders.contains(this)) { + Thread.interrupted(); // Reset the interrupted flag + DecodingImageSource isrc = getWaitingImageSource(); + if (isrc != null) { + try { + isrc.load(); + } catch (Exception e) { + e.printStackTrace(); + } + } else { + break; // Don't wait if timeout expired - terminate loader + } + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + synchronized(storage.loaders) { + storage.loaders.remove(Thread.currentThread()); + } + } + } + + /** + * Removes current thread from loaders (so we are able + * to create more loaders) and decreases its priority. + */ + static void beginAnimation() { + ImageLoadersStorage storage = ImageLoadersStorage.getStorage(); + Thread currThread = Thread.currentThread(); + + synchronized(storage) { + storage.loaders.remove(currThread); + + if (storage.freeLoaders < storage.queue.size()) { + createLoader(); + } + } + + currThread.setPriority(Thread.MIN_PRIORITY); + } + + /** + * Sends the current thread to wait for the new images to load + * if there are free placeholders for loaders + */ + static void endAnimation() { + ImageLoadersStorage storage = ImageLoadersStorage.getStorage(); + Thread currThread = Thread.currentThread(); + + synchronized(storage) { + if (storage.loaders.size() < ImageLoadersStorage.MAX_THREADS && + !storage.loaders.contains(currThread) + ) { + storage.loaders.add(currThread); + } + } + + currThread.setPriority(Thread.NORM_PRIORITY); + } +} \ No newline at end of file diff --git a/awt/org/apache/harmony/awt/gl/image/JpegDecoder.java b/awt/org/apache/harmony/awt/gl/image/JpegDecoder.java new file mode 100644 index 0000000..2e64427 --- /dev/null +++ b/awt/org/apache/harmony/awt/gl/image/JpegDecoder.java @@ -0,0 +1,231 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one or more +* contributor license agreements. See the NOTICE file distributed with +* this work for additional information regarding copyright ownership. +* The ASF licenses this file to You under the Apache License, Version 2.0 +* (the "License"); you may not use this file except in compliance with +* the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +/** + * @author Oleg V. Khaschansky + * @version $Revision$ + */ +package org.apache.harmony.awt.gl.image; + +import java.awt.image.*; +import java.awt.color.ColorSpace; +import java.awt.*; +import java.io.IOException; +import java.io.InputStream; +import java.util.Hashtable; + +import org.apache.harmony.awt.internal.nls.Messages; + +public class JpegDecoder extends ImageDecoder { + // Only 2 output colorspaces expected. Others are converted into + // these ones. + // 1. Grayscale + public static final int JCS_GRAYSCALE = 1; + // 2. RGB + public static final int JCS_RGB = 2; + + // Flags for the consumer, progressive JPEG + private static final int hintflagsProgressive = + ImageConsumer.SINGLEFRAME | // JPEG is a static image + ImageConsumer.TOPDOWNLEFTRIGHT | // This order is only one possible + ImageConsumer.COMPLETESCANLINES; // Don't deliver incomplete scanlines + // Flags for the consumer, singlepass JPEG + private static final int hintflagsSingle = + ImageConsumer.SINGLEPASS | + hintflagsProgressive; + + // Buffer for the stream + private static final int BUFFER_SIZE = 1024; + private byte buffer[] = new byte[BUFFER_SIZE]; + + // 3 possible color models only + private static ColorModel cmRGB; + private static ColorModel cmGray; + + // initializes proper field IDs + private static native void initIDs(); + + // Pointer to native structure which store decoding state + // between subsequent decoding/IO-suspension cycles + private long hNativeDecoder = 0; // NULL initially + + private boolean headerDone = false; + + // Next 4 members are filled by the native method (decompress). + // We can simply check if imageWidth is still negative to find + // out if they are already filled. + private int imageWidth = -1; + private int imageHeight = -1; + private boolean progressive = false; + private int jpegColorSpace = 0; + + // Stores number of bytes consumed by the native decoder + private int bytesConsumed = 0; + // Stores current scanline returned by the decoder + private int currScanline = 0; + + private ColorModel cm = null; + + static { + System.loadLibrary("jpegdecoder"); //$NON-NLS-1$ + + cmGray = new ComponentColorModel( + ColorSpace.getInstance(ColorSpace.CS_GRAY), + false, false, + Transparency.OPAQUE, DataBuffer.TYPE_BYTE + ); + + // Create RGB color model + cmRGB = new DirectColorModel(24, 0xFF0000, 0xFF00, 0xFF); + + initIDs(); + } + + public JpegDecoder(DecodingImageSource src, InputStream is) { + super(src, is); + } + + /* + public JpegDecoder(InputStream iStream, ImageConsumer iConsumer) { + inputStream = iStream; + consumer = iConsumer; + } + */ + + /** + * @return - not NULL if call is successful + */ + private native Object decode( + byte[] input, + int bytesInBuffer, + long hDecoder); + + private static native void releaseNativeDecoder(long hDecoder); + + @Override + public void decodeImage() throws IOException { + try { + int bytesRead = 0, dataLength = 0; + boolean eosReached = false; + int needBytes, offset, bytesInBuffer = 0; + byte byteOut[] = null; + int intOut[] = null; + // Read from the input stream + for (;;) { + needBytes = BUFFER_SIZE - bytesInBuffer; + offset = bytesInBuffer; + + bytesRead = inputStream.read(buffer, offset, needBytes); + + if (bytesRead < 0) { + bytesRead = 0;//break; + eosReached = true; + } // Don't break, maybe something left in buffer + + // Keep track on how much bytes left in buffer + bytesInBuffer += bytesRead; + + // Here we pass overall number of bytes left in the java buffer + // (bytesInBuffer) since jpeg decoder has its own buffer and consumes + // as many bytes as it can. If there are any unconsumed bytes + // it didn't add them to its buffer... + Object arr = decode( + buffer, + bytesInBuffer, + hNativeDecoder); + + // Keep track on how much bytes left in buffer + bytesInBuffer -= bytesConsumed; + + if (!headerDone && imageWidth != -1) { + returnHeader(); + headerDone = true; + } + + if (bytesConsumed < 0) { + break; // Error exit + } + + if (arr instanceof byte[]) { + byteOut = (byte[]) arr; + dataLength = byteOut.length; + returnData(byteOut, currScanline); + } else if (arr instanceof int[]) { + intOut = (int[]) arr; + dataLength = intOut.length; + returnData(intOut, currScanline); + } else { + dataLength = 0; + } + + if (hNativeDecoder == 0) { + break; + } + + if (dataLength == 0 && eosReached) { + releaseNativeDecoder(hNativeDecoder); + break; // Probably image is truncated + } + } + imageComplete(ImageConsumer.STATICIMAGEDONE); + } catch (IOException e) { + throw e; + } finally { + closeStream(); + } + } + + public void returnHeader() { + setDimensions(imageWidth, imageHeight); + + switch (jpegColorSpace) { + case JCS_GRAYSCALE: cm = cmGray; break; + case JCS_RGB: cm = cmRGB; break; + default: + // awt.3D=Unknown colorspace + throw new IllegalArgumentException(Messages.getString("awt.3D")); //$NON-NLS-1$ + } + setColorModel(cm); + + setHints(progressive ? hintflagsProgressive : hintflagsSingle); + + setProperties(new Hashtable()); // Empty + } + + // Send the data to the consumer + public void returnData(int data[], int currScanLine) { + // Send 1 or more scanlines to the consumer. + int numScanlines = data.length / imageWidth; + if (numScanlines > 0) { + setPixels( + 0, currScanLine - numScanlines, + imageWidth, numScanlines, + cm, data, 0, imageWidth + ); + } + } + + public void returnData(byte data[], int currScanLine) { + int numScanlines = data.length / imageWidth; + if (numScanlines > 0) { + setPixels( + 0, currScanLine - numScanlines, + imageWidth, numScanlines, + cm, data, 0, imageWidth + ); + } + } +} diff --git a/awt/org/apache/harmony/awt/gl/image/OffscreenImage.java b/awt/org/apache/harmony/awt/gl/image/OffscreenImage.java new file mode 100644 index 0000000..3445f8e --- /dev/null +++ b/awt/org/apache/harmony/awt/gl/image/OffscreenImage.java @@ -0,0 +1,532 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Igor V. Stolyarov + * @version $Revision$ + */ +/* + * Created on 22.12.2004 + * + */ +package org.apache.harmony.awt.gl.image; + +import java.awt.Graphics; +import java.awt.Image; +import java.awt.image.BufferedImage; +import java.awt.image.ColorModel; +import java.awt.image.ComponentColorModel; +import java.awt.image.DataBuffer; +import java.awt.image.DataBufferByte; +import java.awt.image.DataBufferInt; +import java.awt.image.DirectColorModel; +import java.awt.image.ImageConsumer; +import java.awt.image.ImageObserver; +import java.awt.image.ImageProducer; +import java.awt.image.IndexColorModel; +import java.awt.image.WritableRaster; +import java.util.Hashtable; +import java.util.Vector; + +import org.apache.harmony.awt.gl.ImageSurface; +import org.apache.harmony.awt.internal.nls.Messages; + + +/** + * This class represent implementation of abstract Image class + */ +public class OffscreenImage extends Image implements ImageConsumer { + + static final ColorModel rgbCM = ColorModel.getRGBdefault(); + ImageProducer src; + BufferedImage image; + ColorModel cm; + WritableRaster raster; + boolean isIntRGB; + Hashtable properties; + Vector observers; + int width; + int height; + int imageState; + int hints; + private boolean producing; + private ImageSurface imageSurf; + + public OffscreenImage(ImageProducer ip){ + imageState = 0; + src = ip; + width = -1; + height = -1; + observers = new Vector(); + producing = false; + } + + @Override + public Object getProperty(String name, ImageObserver observer) { + if(name == null) { + // awt.38=Property name is not defined + throw new NullPointerException(Messages.getString("awt.38")); //$NON-NLS-1$ + } + if(properties == null){ + addObserver(observer); + startProduction(); + if(properties == null) { + return null; + } + } + Object prop = properties.get(name); + if(prop == null) { + prop = UndefinedProperty; + } + return prop; + } + + @Override + public ImageProducer getSource() { + return src; + } + + @Override + public int getWidth(ImageObserver observer) { + if((imageState & ImageObserver.WIDTH) == 0){ + addObserver(observer); + startProduction(); + if((imageState & ImageObserver.WIDTH) == 0) { + return -1; + } + } + return width; + } + + @Override + public int getHeight(ImageObserver observer) { + if((imageState & ImageObserver.HEIGHT) == 0){ + addObserver(observer); + startProduction(); + if((imageState & ImageObserver.HEIGHT) == 0) { + return -1; + } + } + return height; + } + + @Override + public Graphics getGraphics() { + // awt.39=This method is not implemented for image obtained from ImageProducer + throw new UnsupportedOperationException(Messages.getString("awt.39")); //$NON-NLS-1$ + } + + @Override + public void flush() { + stopProduction(); + imageUpdate(this, ImageObserver.ABORT, -1, -1, -1, -1); + imageState &= ~ImageObserver.ERROR; + imageState = 0; + image = null; + cm = null; + raster = null; + hints = 0; + width = -1; + height = -1; + } + + public void setProperties(Hashtable properties) { + this.properties = properties; + imageUpdate(this, ImageObserver.PROPERTIES, 0, 0, width, height); + } + + public void setColorModel(ColorModel cm) { + this.cm = cm; + } + + /* + * We suppose what in case loading JPEG image then image has DirectColorModel + * and for infill image Raster will use setPixels method with int array. + * + * In case loading GIF image, for raster infill, is used setPixels method with + * byte array and Color Model is IndexColorModel. But Color Model may + * be changed during this process. Then is called setPixels method with + * int array and image force to default color model - int ARGB. The rest + * pixels are sending in DirectColorModel. + */ + public void setPixels(int x, int y, int w, int h, ColorModel model, + int[] pixels, int off, int scansize) { + if(raster == null){ + if(cm == null){ + if(model == null) { + // awt.3A=Color Model is null + throw new NullPointerException(Messages.getString("awt.3A")); //$NON-NLS-1$ + } + cm = model; + } + createRaster(); + } + + if(model == null) { + model = cm; + } + if(cm != model){ + forceToIntARGB(); + } + + if(cm == model && model.getTransferType() == DataBuffer.TYPE_INT && + raster.getNumDataElements() == 1){ + + DataBufferInt dbi = (DataBufferInt) raster.getDataBuffer(); + int data[] = dbi.getData(); + int scanline = raster.getWidth(); + int rof = dbi.getOffset() + y * scanline + x; + for(int lineOff = off, line = y; line < y + h; + line++, lineOff += scansize, rof += scanline){ + + System.arraycopy(pixels, lineOff, data, rof, w); + } + + }else if(isIntRGB){ + int buff[] = new int[w]; + DataBufferInt dbi = (DataBufferInt) raster.getDataBuffer(); + int data[] = dbi.getData(); + int scanline = raster.getWidth(); + int rof = dbi.getOffset() + y * scanline + x; + for (int sy = y, sOff = off; sy < y + h; sy++, sOff += scansize, + rof += scanline) { + + for (int sx = x, idx = 0; sx < x + w; sx++, idx++) { + buff[idx] = model.getRGB(pixels[sOff + idx]); + } + System.arraycopy(buff, 0, data, rof, w); + } + }else{ + Object buf = null; + for (int sy = y, sOff = off; sy < y + h; sy++, sOff += scansize) { + for (int sx = x, idx = 0; sx < x + w; sx++, idx++) { + int rgb = model.getRGB(pixels[sOff + idx]); + buf = cm.getDataElements(rgb, buf); + raster.setDataElements(sx, sy, buf); + } + } + } + + if (imageSurf != null) { + imageSurf.invalidate(); + } + + imageUpdate(this, ImageObserver.SOMEBITS, 0, 0, width, height); + } + + public void setPixels(int x, int y, int w, int h, ColorModel model, + byte[] pixels, int off, int scansize) { + + if(raster == null){ + if(cm == null){ + if(model == null) { + // awt.3A=Color Model is null + throw new NullPointerException(Messages.getString("awt.3A")); //$NON-NLS-1$ + } + cm = model; + } + createRaster(); + } + if(model == null) { + model = cm; + } + if(model != cm){ + forceToIntARGB(); + } + + if(isIntRGB){ + int buff[] = new int[w]; + IndexColorModel icm = (IndexColorModel) model; + int colorMap[] = new int[icm.getMapSize()]; + icm.getRGBs(colorMap); + DataBufferInt dbi = (DataBufferInt) raster.getDataBuffer(); + int data[] = dbi.getData(); + int scanline = raster.getWidth(); + int rof = dbi.getOffset() + y * scanline + x; + if(model instanceof IndexColorModel){ + + for (int sy = y, sOff = off; sy < y + h; sy++, sOff += scansize, + rof += scanline) { + for (int sx = x, idx = 0; sx < x + w; sx++, idx++) { + buff[idx] = colorMap[pixels[sOff + idx] & 0xff]; + } + System.arraycopy(buff, 0, data, rof, w); + } + }else{ + + for (int sy = y, sOff = off; sy < y + h; sy++, sOff += scansize, + rof += scanline) { + for (int sx = x, idx = 0; sx < x + w; sx++, idx++) { + buff[idx] = model.getRGB(pixels[sOff + idx] & 0xff); + } + System.arraycopy(buff, 0, data, rof, w); + } + } + }else if(model == cm && model.getTransferType() == DataBuffer.TYPE_BYTE && + raster.getNumDataElements() == 1){ + + DataBufferByte dbb = (DataBufferByte)raster.getDataBuffer(); + byte data[] = dbb.getData(); + int scanline = raster.getWidth(); + int rof = dbb.getOffset() + y * scanline + x; + for(int lineOff = off, line = y; line < y + h; + line++, lineOff += scansize, rof += scanline){ + System.arraycopy(pixels, lineOff, data, rof, w); + } + // BEGIN android-added (taken from newer Harmony) + }else if(model == cm && model.getTransferType() == DataBuffer.TYPE_BYTE && + cm instanceof ComponentColorModel){ + + int nc = cm.getNumComponents(); + byte stride[] = new byte[scansize]; + for (int sy = y, sOff = off; sy < y + h; sy++, sOff += scansize) { + System.arraycopy(pixels, sOff, stride, 0, scansize); + + raster.setDataElements(x, sy, w, 1, stride); + } + // END android-added + }else { + for (int sy = y, sOff = off; sy < y + h; sy++, sOff += scansize) { + for (int sx = x, idx = 0; sx < x + w; sx++, idx++) { + int rgb = model.getRGB(pixels[sOff + idx] & 0xff); + raster.setDataElements(sx, sy, cm.getDataElements(rgb, null)); + } + } + } + + if (imageSurf != null) { + imageSurf.invalidate(); + } + + imageUpdate(this, ImageObserver.SOMEBITS, 0, 0, width, height); + } + + public void setDimensions(int width, int height) { + if(width <= 0 || height <= 0){ + imageComplete(ImageObserver.ERROR); + return; + } + + this.width = width; + this.height = height; + imageUpdate(this, (ImageObserver.HEIGHT | ImageObserver.WIDTH), + 0, 0, width, height); + } + + public void setHints(int hints) { + this.hints = hints; + } + + public void imageComplete(int state) { + int flag; + switch(state){ + case IMAGEABORTED: + flag = ImageObserver.ABORT; + break; + case IMAGEERROR: + flag = ImageObserver.ERROR | ImageObserver.ABORT; + break; + case SINGLEFRAMEDONE: + flag = ImageObserver.FRAMEBITS; + break; + case STATICIMAGEDONE: + flag = ImageObserver.ALLBITS; + break; + default: + // awt.3B=Incorrect ImageConsumer completion status + throw new IllegalArgumentException(Messages.getString("awt.3B")); //$NON-NLS-1$ + } + imageUpdate(this, flag, 0, 0, width, height); + + if((flag & (ImageObserver.ERROR | ImageObserver.ABORT | + ImageObserver.ALLBITS)) != 0 ) { + stopProduction(); + observers.removeAllElements(); + } + } + + public /*synchronized*/ BufferedImage getBufferedImage(){ + if(image == null){ + ColorModel model = getColorModel(); + WritableRaster wr = getRaster(); + if(model != null && wr != null) { + image = new BufferedImage(model, wr, model.isAlphaPremultiplied(), null); + } + } + return image; + } + + public /*synchronized*/ int checkImage(ImageObserver observer){ + addObserver(observer); + return imageState; + } + + public /*synchronized*/ boolean prepareImage(ImageObserver observer){ + if((imageState & ImageObserver.ERROR) != 0){ + if(observer != null){ + observer.imageUpdate(this, ImageObserver.ERROR | + ImageObserver.ABORT, -1, -1, -1, -1); + } + return false; + } + if((imageState & ImageObserver.ALLBITS) != 0) { + return true; + } + addObserver(observer); + startProduction(); + return ((imageState & ImageObserver.ALLBITS) != 0); + } + + public /*synchronized*/ ColorModel getColorModel(){ + if(cm == null) { + startProduction(); + } + return cm; + } + + public /*synchronized*/ WritableRaster getRaster(){ + if(raster == null) { + startProduction(); + } + return raster; + } + + public int getState(){ + return imageState; + } + + private /*synchronized*/ void addObserver(ImageObserver observer){ + if(observer != null){ + if(observers.contains(observer)) { + return; + } + if((imageState & ImageObserver.ERROR) != 0){ + observer.imageUpdate(this, ImageObserver.ERROR | + ImageObserver.ABORT, -1, -1, -1, -1); + return; + } + if((imageState & ImageObserver.ALLBITS) != 0){ + observer.imageUpdate(this, imageState, 0, 0, width, height); + return; + } + observers.addElement(observer); + } + } + + private synchronized void startProduction(){ + if(!producing){ + imageState &= ~ImageObserver.ABORT; + producing = true; + src.startProduction(this); + } + } + + private synchronized void stopProduction(){ + producing = false; + src.removeConsumer(this); + } + + private void createRaster(){ + try{ + raster = cm.createCompatibleWritableRaster(width, height); + isIntRGB = false; + if(cm instanceof DirectColorModel){ + DirectColorModel dcm = (DirectColorModel) cm; + if(dcm.getTransferType() == DataBuffer.TYPE_INT && + dcm.getRedMask() == 0xff0000 && + dcm.getGreenMask() == 0xff00 && + dcm.getBlueMask() == 0xff){ + isIntRGB = true; + } + } + }catch(Exception e){ + cm = ColorModel.getRGBdefault(); + raster = cm.createCompatibleWritableRaster(width, height); + isIntRGB = true; + } + } + + private /*synchronized*/ void imageUpdate(Image img, int infoflags, int x, int y, + int width, int height){ + + imageState |= infoflags; + for (ImageObserver observer : observers) { + observer.imageUpdate(this, infoflags, x, y, width, height); + } + +// notifyAll(); + } + + private void forceToIntARGB(){ + + int w = raster.getWidth(); + int h = raster.getHeight(); + + WritableRaster destRaster = rgbCM.createCompatibleWritableRaster(w, h); + + Object obj = null; + int pixels[] = new int[w]; + + if(cm instanceof IndexColorModel){ + IndexColorModel icm = (IndexColorModel) cm; + int colorMap[] = new int[icm.getMapSize()]; + icm.getRGBs(colorMap); + + for (int y = 0; y < h; y++) { + obj = raster.getDataElements(0, y, w, 1, obj); + byte ba[] = (byte[]) obj; + for (int x = 0; x < ba.length; x++) { + pixels[x] = colorMap[ba[x] & 0xff]; + } + destRaster.setDataElements(0, y, w, 1, pixels); + } + + }else{ + for(int y = 0; y < h; y++){ + for(int x = 0; x < w; x++){ + obj = raster.getDataElements(x, y, obj); + pixels[x] = cm.getRGB(obj); + } + destRaster.setDataElements(0, y, w, 1, pixels); + } + } + + synchronized(this){ + if(imageSurf != null){ + imageSurf.dispose(); + imageSurf = null; + } + if(image != null){ + image.flush(); + image = null; + } + cm = rgbCM; + raster = destRaster; + isIntRGB = true; + } + } + + public ImageSurface getImageSurface() { + if (imageSurf == null) { + ColorModel model = getColorModel(); + WritableRaster wr = getRaster(); + if(model != null && wr != null) { + imageSurf = new ImageSurface(model, wr); + } + } + return imageSurf; + } +} diff --git a/awt/org/apache/harmony/awt/gl/image/OrdinaryWritableRaster.java b/awt/org/apache/harmony/awt/gl/image/OrdinaryWritableRaster.java new file mode 100644 index 0000000..1748e1b --- /dev/null +++ b/awt/org/apache/harmony/awt/gl/image/OrdinaryWritableRaster.java @@ -0,0 +1,153 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Igor V. Stolyarov + * @version $Revision$ + */ +/* + * Created on 30.09.2004 + * + */ +package org.apache.harmony.awt.gl.image; + +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.image.DataBuffer; +import java.awt.image.Raster; +import java.awt.image.SampleModel; +import java.awt.image.WritableRaster; + +public class OrdinaryWritableRaster extends WritableRaster { + + public OrdinaryWritableRaster(SampleModel sampleModel, + DataBuffer dataBuffer, Rectangle aRegion, + Point sampleModelTranslate, WritableRaster parent) { + super(sampleModel, dataBuffer, aRegion, sampleModelTranslate, parent); + } + + public OrdinaryWritableRaster(SampleModel sampleModel, + DataBuffer dataBuffer, Point origin) { + super(sampleModel, dataBuffer, origin); + } + + public OrdinaryWritableRaster(SampleModel sampleModel, Point origin) { + super(sampleModel, origin); + } + + @Override + public void setDataElements(int x, int y, Object inData) { + super.setDataElements(x, y, inData); + } + + @Override + public void setDataElements(int x, int y, int w, int h, Object inData) { + super.setDataElements(x, y, w, h, inData); + } + + @Override + public WritableRaster createWritableChild(int parentX, int parentY, int w, + int h, int childMinX, int childMinY, int[] bandList) { + return super.createWritableChild(parentX, parentY, w, h, childMinX, + childMinY, bandList); + } + + @Override + public WritableRaster createWritableTranslatedChild(int childMinX, + int childMinY) { + return super.createWritableTranslatedChild(childMinX, childMinY); + } + + @Override + public WritableRaster getWritableParent() { + return super.getWritableParent(); + } + + @Override + public void setRect(Raster srcRaster) { + super.setRect(srcRaster); + } + + @Override + public void setRect(int dx, int dy, Raster srcRaster) { + super.setRect(dx, dy, srcRaster); + } + + @Override + public void setDataElements(int x, int y, Raster inRaster) { + super.setDataElements(x, y, inRaster); + } + + @Override + public void setPixel(int x, int y, int[] iArray) { + super.setPixel(x, y, iArray); + } + + @Override + public void setPixel(int x, int y, float[] fArray) { + super.setPixel(x, y, fArray); + } + + @Override + public void setPixel(int x, int y, double[] dArray) { + super.setPixel(x, y, dArray); + } + + @Override + public void setPixels(int x, int y, int w, int h, int[] iArray) { + super.setPixels(x, y, w, h, iArray); + } + + @Override + public void setPixels(int x, int y, int w, int h, float[] fArray) { + super.setPixels(x, y, w, h, fArray); + } + + @Override + public void setPixels(int x, int y, int w, int h, double[] dArray) { + super.setPixels(x, y, w, h, dArray); + } + + @Override + public void setSamples(int x, int y, int w, int h, int b, int[] iArray) { + super.setSamples(x, y, w, h, b, iArray); + } + + @Override + public void setSamples(int x, int y, int w, int h, int b, float[] fArray) { + super.setSamples(x, y, w, h, b, fArray); + } + + @Override + public void setSamples(int x, int y, int w, int h, int b, double[] dArray) { + super.setSamples(x, y, w, h, b, dArray); + } + + @Override + public void setSample(int x, int y, int b, int s) { + super.setSample(x, y, b, s); + } + + @Override + public void setSample(int x, int y, int b, float s) { + super.setSample(x, y, b, s); + } + + @Override + public void setSample(int x, int y, int b, double s) { + super.setSample(x, y, b, s); + } +} \ No newline at end of file diff --git a/awt/org/apache/harmony/awt/gl/image/PngDecoder.java b/awt/org/apache/harmony/awt/gl/image/PngDecoder.java new file mode 100644 index 0000000..7e85600 --- /dev/null +++ b/awt/org/apache/harmony/awt/gl/image/PngDecoder.java @@ -0,0 +1,270 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Oleg V. Khaschansky + * @version $Revision$ + * + * @date: Jul 22, 2005 + */ + +package org.apache.harmony.awt.gl.image; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Hashtable; +import java.awt.color.ColorSpace; +import java.awt.image.*; +import java.awt.*; + +import org.apache.harmony.awt.internal.nls.Messages; + +public class PngDecoder extends ImageDecoder { + // initializes proper field IDs + private static native void initIDs(); + + static { + System.loadLibrary("gl"); //$NON-NLS-1$ + initIDs(); + } + + private static final int hintflags = + ImageConsumer.SINGLEFRAME | // PNG is a static image + ImageConsumer.TOPDOWNLEFTRIGHT | // This order is only one possible + ImageConsumer.COMPLETESCANLINES; // Don't deliver incomplete scanlines + + // Each pixel is a grayscale sample. + private static final int PNG_COLOR_TYPE_GRAY = 0; + // Each pixel is an R,G,B triple. + private static final int PNG_COLOR_TYPE_RGB = 2; + // Each pixel is a palette index, a PLTE chunk must appear. + private static final int PNG_COLOR_TYPE_PLTE = 3; + // Each pixel is a grayscale sample, followed by an alpha sample. + private static final int PNG_COLOR_TYPE_GRAY_ALPHA = 4; + // Each pixel is an R,G,B triple, followed by an alpha sample. + private static final int PNG_COLOR_TYPE_RGBA = 6; + + private static final int INPUT_BUFFER_SIZE = 4096; + private byte buffer[] = new byte[INPUT_BUFFER_SIZE]; + + // Buffers for decoded image data + byte byteOut[]; + int intOut[]; + + // Native pointer to png decoder data + private long hNativeDecoder; + + int imageWidth, imageHeight; + int colorType; + int bitDepth; + byte cmap[]; + + boolean transferInts; // Is transfer type int?.. or byte? + int dataElementsPerPixel = 1; + + ColorModel cm; + + int updateFromScanline; // First scanline to update + int numScanlines; // Number of scanlines to update + + private native long decode(byte[] input, int bytesInBuffer, long hDecoder); + + private static native void releaseNativeDecoder(long hDecoder); + + public PngDecoder(DecodingImageSource src, InputStream is) { + super(src, is); + } + + @Override + public void decodeImage() throws IOException { + try { + int bytesRead = 0; + int needBytes, offset, bytesInBuffer = 0; + // Read from the input stream + for (;;) { + needBytes = INPUT_BUFFER_SIZE - bytesInBuffer; + offset = bytesInBuffer; + + bytesRead = inputStream.read(buffer, offset, needBytes); + + if (bytesRead < 0) { // Break, nothing to read from buffer, image truncated? + releaseNativeDecoder(hNativeDecoder); + break; + } + + // Keep track on how much bytes left in buffer + bytesInBuffer += bytesRead; + hNativeDecoder = decode(buffer, bytesInBuffer, hNativeDecoder); + // PNG decoder always consumes all bytes at once + bytesInBuffer = 0; + + // if (bytesConsumed < 0) + //break; // Error exit + + returnData(); + + // OK, we decoded all the picture in the right way... + if (hNativeDecoder == 0) { + break; + } + } + + imageComplete(ImageConsumer.STATICIMAGEDONE); + } catch (IOException e) { + throw e; + } catch (RuntimeException e) { + imageComplete(ImageConsumer.IMAGEERROR); + throw e; + } finally { + closeStream(); + } + } + + @SuppressWarnings("unused") + private void returnHeader() { // Called from native code + setDimensions(imageWidth, imageHeight); + + switch (colorType) { + case PNG_COLOR_TYPE_GRAY: { + if (bitDepth != 8 && bitDepth != 4 && bitDepth != 2 && bitDepth != 1) { + // awt.3C=Unknown PNG color type + throw new IllegalArgumentException(Messages.getString("awt.3C")); //$NON-NLS-1$ + } + + // Create gray color model + int numEntries = 1 << bitDepth; + int scaleFactor = 255 / (numEntries-1); + byte comps[] = new byte[numEntries]; + for (int i = 0; i < numEntries; i++) { + comps[i] = (byte) (i * scaleFactor); + } + cm = new IndexColorModel(/*bitDepth*/8, numEntries, comps, comps, comps); + + transferInts = false; + break; + } + + case PNG_COLOR_TYPE_RGB: { + if (bitDepth != 8) { + // awt.3C=Unknown PNG color type + throw new IllegalArgumentException(Messages.getString("awt.3C")); //$NON-NLS-1$ + } + + cm = new DirectColorModel(24, 0xFF0000, 0xFF00, 0xFF); + + transferInts = true; + break; + } + + case PNG_COLOR_TYPE_PLTE: { + if (bitDepth != 8 && bitDepth != 4 && bitDepth != 2 && bitDepth != 1) { + // awt.3C=Unknown PNG color type + throw new IllegalArgumentException(Messages.getString("awt.3C")); //$NON-NLS-1$ + } + + cm = new IndexColorModel(/*bitDepth*/8, cmap.length / 3, cmap, 0, false); + + transferInts = false; + break; + } + + case PNG_COLOR_TYPE_GRAY_ALPHA: { + if (bitDepth != 8) { + // awt.3C=Unknown PNG color type + throw new IllegalArgumentException(Messages.getString("awt.3C")); //$NON-NLS-1$ + } + + cm = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_GRAY), + true, false, + Transparency.TRANSLUCENT, + DataBuffer.TYPE_BYTE); + + transferInts = false; + dataElementsPerPixel = 2; + break; + } + + case PNG_COLOR_TYPE_RGBA: { + if (bitDepth != 8) { + // awt.3C=Unknown PNG color type + throw new IllegalArgumentException(Messages.getString("awt.3C")); //$NON-NLS-1$ + } + + cm = ColorModel.getRGBdefault(); + + transferInts = true; + break; + } + default: + // awt.3C=Unknown PNG color type + throw new IllegalArgumentException(Messages.getString("awt.3C")); //$NON-NLS-1$ + } + + // Create output buffer + if (transferInts) { + intOut = new int[imageWidth * imageHeight]; + } else { + byteOut = new byte[imageWidth * imageHeight * dataElementsPerPixel]; + } + + setColorModel(cm); + + setHints(hintflags); + setProperties(new Hashtable()); // Empty + } + + // Send the data to the consumer + private void returnData() { + // Send 1 or more scanlines to the consumer. + if (numScanlines > 0) { + // Native decoder could have returned + // some data from the next pass, handle it here + int pass1, pass2; + if (updateFromScanline + numScanlines > imageHeight) { + pass1 = imageHeight - updateFromScanline; + pass2 = updateFromScanline + numScanlines - imageHeight; + } else { + pass1 = numScanlines; + pass2 = 0; + } + + transfer(updateFromScanline, pass1); + if (pass2 != 0) { + transfer(0, pass2); + } + } + } + + private void transfer(int updateFromScanline, int numScanlines) { + if (transferInts) { + setPixels( + 0, updateFromScanline, + imageWidth, numScanlines, + cm, intOut, + updateFromScanline * imageWidth, + imageWidth + ); + } else { + setPixels( + 0, updateFromScanline, + imageWidth, numScanlines, + cm, byteOut, + updateFromScanline * imageWidth * dataElementsPerPixel, + imageWidth * dataElementsPerPixel + ); + } + } +} diff --git a/awt/org/apache/harmony/awt/gl/image/PngDecoderJava.java b/awt/org/apache/harmony/awt/gl/image/PngDecoderJava.java new file mode 100644 index 0000000..46545f9 --- /dev/null +++ b/awt/org/apache/harmony/awt/gl/image/PngDecoderJava.java @@ -0,0 +1,282 @@ +package org.apache.harmony.awt.gl.image; + +// A simple PNG decoder source code in Java. +import java.awt.Graphics; +import java.awt.Insets; +import java.awt.image.BufferedImage; +import java.awt.image.ColorModel; +import java.awt.image.DataBuffer; +import java.awt.image.DataBufferByte; +import java.awt.image.IndexColorModel; +import java.awt.image.Raster; +import java.awt.image.WritableRaster; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.util.zip.CRC32; +import java.util.zip.InflaterInputStream; + +//import javax.swing.JFrame; + +public class PngDecoderJava { + +/* + public static void main(String[] args) throws Exception { + String name = "logo.png"; + if (args.length > 0) + name = args[0]; + InputStream in = PngDecoderJava.class.getResourceAsStream(name); + final BufferedImage image = PngDecoderJava.decode(in); + in.close(); + + JFrame f = new JFrame() { + public void paint(Graphics g) { + Insets insets = getInsets(); + g.drawImage(image, insets.left, insets.top, null); + } + }; + f.setVisible(true); + Insets insets = f.getInsets(); + f.setSize(image.getWidth() + insets.left + insets.right, image + .getHeight() + + insets.top + insets.bottom); + } + */ + + public static BufferedImage decode(InputStream in) throws IOException { + DataInputStream dataIn = new DataInputStream(in); + readSignature(dataIn); + PNGData chunks = readChunks(dataIn); + + long widthLong = chunks.getWidth(); + long heightLong = chunks.getHeight(); + if (widthLong > Integer.MAX_VALUE || heightLong > Integer.MAX_VALUE) + throw new IOException("That image is too wide or tall."); + int width = (int) widthLong; + int height = (int) heightLong; + + ColorModel cm = chunks.getColorModel(); + WritableRaster raster = chunks.getRaster(); + + BufferedImage image = new BufferedImage(cm, raster, false, null); + + return image; + } + + protected static void readSignature(DataInputStream in) throws IOException { + long signature = in.readLong(); + if (signature != 0x89504e470d0a1a0aL) + throw new IOException("PNG signature not found!"); + } + + protected static PNGData readChunks(DataInputStream in) throws IOException { + PNGData chunks = new PNGData(); + + boolean trucking = true; + while (trucking) { + try { + // Read the length. + int length = in.readInt(); + if (length < 0) + throw new IOException("Sorry, that file is too long."); + // Read the type. + byte[] typeBytes = new byte[4]; + in.readFully(typeBytes); + // Read the data. + byte[] data = new byte[length]; + in.readFully(data); + // Read the CRC. + long crc = in.readInt() & 0x00000000ffffffffL; // Make it + // unsigned. + if (verifyCRC(typeBytes, data, crc) == false) + throw new IOException("That file appears to be corrupted."); + + PNGChunk chunk = new PNGChunk(typeBytes, data); + chunks.add(chunk); + } catch (EOFException eofe) { + trucking = false; + } + } + return chunks; + } + + protected static boolean verifyCRC(byte[] typeBytes, byte[] data, long crc) { + CRC32 crc32 = new CRC32(); + crc32.update(typeBytes); + crc32.update(data); + long calculated = crc32.getValue(); + return (calculated == crc); + } +} + +class PNGData { + private int mNumberOfChunks; + + private PNGChunk[] mChunks; + + public PNGData() { + mNumberOfChunks = 0; + mChunks = new PNGChunk[10]; + } + + public void add(PNGChunk chunk) { + mChunks[mNumberOfChunks++] = chunk; + if (mNumberOfChunks >= mChunks.length) { + PNGChunk[] largerArray = new PNGChunk[mChunks.length + 10]; + System.arraycopy(mChunks, 0, largerArray, 0, mChunks.length); + mChunks = largerArray; + } + } + + public long getWidth() { + return getChunk("IHDR").getUnsignedInt(0); + } + + public long getHeight() { return getChunk("IHDR").getUnsignedInt(4); + } + + public short getBitsPerPixel() { + return getChunk("IHDR").getUnsignedByte(8); + } + + public short getColorType() { + return getChunk("IHDR").getUnsignedByte(9); + } + + public short getCompression() { + return getChunk("IHDR").getUnsignedByte(10); + } + + public short getFilter() { + return getChunk("IHDR").getUnsignedByte(11); + } + + public short getInterlace() { + return getChunk("IHDR").getUnsignedByte(12); + } + + public ColorModel getColorModel() { + short colorType = getColorType(); + int bitsPerPixel = getBitsPerPixel(); + + if (colorType == 3) { + byte[] paletteData = getChunk("PLTE").getData(); + int paletteLength = paletteData.length / 3; + return new IndexColorModel(bitsPerPixel, paletteLength, + paletteData, 0, false); + } + System.out.println("Unsupported color type: " + colorType); + return null; + } + + public WritableRaster getRaster() { + int width = (int) getWidth(); + int height = (int) getHeight(); + int bitsPerPixel = getBitsPerPixel(); + short colorType = getColorType(); + + if (colorType == 3) { + byte[] imageData = getImageData(); + //Orig: DataBuffer db = new DataBufferByte(imageData, imageData.length); + int len = Math.max(imageData.length, (width - 1) * (height -1)); + DataBuffer db = new DataBufferByte(imageData, len); + WritableRaster raster = Raster.createPackedRaster(db, width, + height, bitsPerPixel, null); + return raster; + } else + System.out.println("Unsupported color type!"); + return null; + } + + public byte[] getImageData() { + try { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + // Write all the IDAT data into the array. + for (int i = 0; i < mNumberOfChunks; i++) { + PNGChunk chunk = mChunks[i]; + if (chunk.getTypeString().equals("IDAT")) { + out.write(chunk.getData()); + } + } + out.flush(); + // Now deflate the data. + InflaterInputStream in = new InflaterInputStream( + new ByteArrayInputStream(out.toByteArray())); + ByteArrayOutputStream inflatedOut = new ByteArrayOutputStream(); + int readLength; + byte[] block = new byte[8192]; + while ((readLength = in.read(block)) != -1) + inflatedOut.write(block, 0, readLength); + inflatedOut.flush(); + byte[] imageData = inflatedOut.toByteArray(); + // Compute the real length. + int width = (int) getWidth(); + int height = (int) getHeight(); + int bitsPerPixel = getBitsPerPixel(); + int length = width * height * bitsPerPixel / 8; + + byte[] prunedData = new byte[length]; + + // We can only deal with non-interlaced images. + if (getInterlace() == 0) { + int index = 0; + for (int i = 0; i < length; i++) { + if ((i * 8 / bitsPerPixel) % width == 0) { + index++; // Skip the filter byte. + } + prunedData[i] = imageData[index++]; + } + } else + System.out.println("Couldn't undo interlacing."); + + return prunedData; + } catch (IOException ioe) { + } + return null; + } + + public PNGChunk getChunk(String type) { + for (int i = 0; i < mNumberOfChunks; i++) + if (mChunks[i].getTypeString().equals(type)) + return mChunks[i]; + return null; + } +} + +class PNGChunk { + private byte[] mType; + + private byte[] mData; + + public PNGChunk(byte[] type, byte[] data) { + mType = type; + mData = data; + } + + public String getTypeString() { + try { + return new String(mType, "UTF8"); + } catch (UnsupportedEncodingException uee) { + return ""; + } + } + + public byte[] getData() { + return mData; + } + + public long getUnsignedInt(int offset) { + long value = 0; + for (int i = 0; i < 4; i++) + value += (mData[offset + i] & 0xff) << ((3 - i) * 8); + return value; + } + + public short getUnsignedByte(int offset) { + return (short) (mData[offset] & 0x00ff); + } +} \ No newline at end of file diff --git a/awt/org/apache/harmony/awt/gl/image/URLDecodingImageSource.java b/awt/org/apache/harmony/awt/gl/image/URLDecodingImageSource.java new file mode 100644 index 0000000..a1899d6 --- /dev/null +++ b/awt/org/apache/harmony/awt/gl/image/URLDecodingImageSource.java @@ -0,0 +1,77 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Igor V. Stolyarov + * @version $Revision$ + */ +/* + * Created on 10.02.2005 + * + */ +package org.apache.harmony.awt.gl.image; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.net.URLConnection; +import java.security.Permission; + +public class URLDecodingImageSource extends DecodingImageSource { + + URL url; + + public URLDecodingImageSource(URL url){ + SecurityManager security = System.getSecurityManager(); + if (security != null) { + security.checkConnect(url.getHost(), url.getPort()); + try { + Permission p = url.openConnection().getPermission(); + security.checkPermission(p); + } catch (IOException e) { + } + } + this.url = url; + } + + @Override + protected boolean checkConnection() { + SecurityManager security = System.getSecurityManager(); + if (security != null) { + try { + security.checkConnect(url.getHost(), url.getPort()); + return true; + } catch (SecurityException e) { + return false; + } + } + return true; + } + + @Override + protected InputStream getInputStream() { + try{ + URLConnection uc = url.openConnection(); + // BEGIN android-modified + return new BufferedInputStream(uc.getInputStream(), 8192); + // END android-modified + }catch(IOException e){ + return null; + } + } + +} diff --git a/awt/org/apache/harmony/awt/gl/render/Blitter.java b/awt/org/apache/harmony/awt/gl/render/Blitter.java new file mode 100644 index 0000000..3b8012e --- /dev/null +++ b/awt/org/apache/harmony/awt/gl/render/Blitter.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Igor V. Stolyarov + * @version $Revision$ + * Created on 14.11.2005 + * + */ +package org.apache.harmony.awt.gl.render; + +import java.awt.Color; +import java.awt.Composite; +import java.awt.geom.AffineTransform; + +import org.apache.harmony.awt.gl.MultiRectArea; +import org.apache.harmony.awt.gl.Surface; + +/** + * The interface for objects which can drawing Images on other Images which have + * Graphics or on the display. + */ +public interface Blitter { + + public abstract void blit(int srcX, int srcY, Surface srcSurf, + int dstX, int dstY, Surface dstSurf, int width, int height, + AffineTransform sysxform, AffineTransform xform, + Composite comp, Color bgcolor, + MultiRectArea clip); + + public abstract void blit(int srcX, int srcY, Surface srcSurf, + int dstX, int dstY, Surface dstSurf, int width, int height, + AffineTransform sysxform, Composite comp, Color bgcolor, + MultiRectArea clip); + + public abstract void blit(int srcX, int srcY, Surface srcSurf, + int dstX, int dstY, Surface dstSurf, int width, int height, + Composite comp, Color bgcolor, MultiRectArea clip); + +} diff --git a/awt/org/apache/harmony/awt/gl/render/JavaArcRasterizer.java b/awt/org/apache/harmony/awt/gl/render/JavaArcRasterizer.java new file mode 100644 index 0000000..b643b41 --- /dev/null +++ b/awt/org/apache/harmony/awt/gl/render/JavaArcRasterizer.java @@ -0,0 +1,502 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Denis M. Kishenko + * @version $Revision$ + */ +package org.apache.harmony.awt.gl.render; + +import org.apache.harmony.awt.gl.MultiRectArea; + +public class JavaArcRasterizer { + + /** + * Adds particular arc segment to mra + */ + static void addX0LineSeg(MultiRectArea mra, int[] line, int cx, int cy, int b, int start, int finish) { + int x1 = 0; + for(int i = 0; i < line.length; i++) { + int x2 = line[i]; + int y = cy + (b - i); + if (x1 <= finish && x2 >= start) { + mra.addRect(cx + Math.max(x1, start), y, cx + Math.min(x2, finish), y); + } + x1 = x2 + 1; + } + } + + static void addX1LineSeg(MultiRectArea mra, int[] line, int cx, int cy, int b, int start, int finish) { + int x1 = 0; + for(int i = 0; i < line.length; i++) { + int x2 = line[i]; + int y = cy - (b - i); + if (x1 <= finish && x2 >= start) { + mra.addRect(cx + Math.max(x1, start), y, cx + Math.min(x2, finish), y); + } + x1 = x2 + 1; + } + } + + static void addX2LineSeg(MultiRectArea mra, int[] line, int cx, int cy, int b, int start, int finish) { + int x1 = 0; + for(int i = 0; i < line.length; i++) { + int x2 = line[i]; + int y = cy - (b - i); + if (x1 <= finish && x2 >= start) { + mra.addRect(cx - Math.min(x2, finish), y, cx - Math.max(x1, start), y); + } + x1 = x2 + 1; + } + } + + static void addX3LineSeg(MultiRectArea mra, int[] line, int cx, int cy, int b, int start, int finish) { + int x1 = 0; + for(int i = 0; i < line.length; i++) { + int x2 = line[i]; + int y = cy + (b - i); + if (x1 <= finish && x2 >= start) { + mra.addRect(cx - Math.min(x2, finish), y, cx - Math.max(x1, start), y); + } + x1 = x2 + 1; + } + } + + static void addY0LineSeg(MultiRectArea mra, int[] line, int cx, int cy, int b, int start, int finish) { + int y1 = 0; + for(int i = 0; i < line.length; i++) { + int x = cx + (b - i); + int y2 = line[i]; + if (y1 <= finish && y2 >= start) { + mra.addRect(x, cy + Math.max(y1, start), x, cy + Math.min(y2, finish)); + } + y1 = y2 + 1; + } + } + + static void addY1LineSeg(MultiRectArea mra, int[] line, int cx, int cy, int b, int start, int finish) { + int y1 = 0; + for(int i = 0; i < line.length; i++) { + int x = cx - (b - i); + int y2 = line[i]; + if (y1 <= finish && y2 >= start) { + mra.addRect(x, cy + Math.max(y1, start), x, cy + Math.min(y2, finish)); + } + y1 = y2 + 1; + } + } + + static void addY2LineSeg(MultiRectArea mra, int[] line, int cx, int cy, int b, int start, int finish) { + int y1 = 0; + for(int i = 0; i < line.length; i++) { + int x = cx - (b - i); + int y2 = line[i]; + if (y1 <= finish && y2 >= start) { + mra.addRect(x, cy - Math.min(y2, finish), x, cy - Math.max(y1, start)); + } + y1 = y2 + 1; + } + } + + static void addY3LineSeg(MultiRectArea mra, int[] line, int cx, int cy, int b, int start, int finish) { + int y1 = 0; + for(int i = 0; i < line.length; i++) { + int x = cx + (b - i); + int y2 = line[i]; + if (y1 <= finish && y2 >= start) { + mra.addRect(x, cy - Math.min(y2, finish), x, cy - Math.max(y1, start)); + } + y1 = y2 + 1; + } + } + + static void addX0Line(MultiRectArea mra, int[] line, int cx, int cy, int b) { + int prev = 0; + for(int i = 0; i < line.length; i++) { + mra.addRect(cx + prev, cy + (b - i), cx + line[i], cy + (b - i)); + prev = line[i] + 1; + } + } + + static void addX1Line(MultiRectArea mra, int[] line, int cx, int cy, int b) { + int prev = 0; + for(int i = 0; i < line.length; i++) { + mra.addRect(cx + prev, cy - (b - i), cx + line[i], cy - (b - i)); + prev = line[i] + 1; + } + } + + static void addX2Line(MultiRectArea mra, int[] line, int cx, int cy, int b) { + int prev = 0; + for(int i = 0; i < line.length; i++) { + mra.addRect(cx - line[i], cy - (b - i), cx - prev, cy - (b - i)); + prev = line[i] + 1; + } + } + + static void addX3Line(MultiRectArea mra, int[] line, int cx, int cy, int b) { + int prev = 0; + for(int i = 0; i < line.length; i++) { + mra.addRect(cx - line[i], cy + (b - i), cx - prev, cy + (b - i)); + prev = line[i] + 1; + } + } + + static void addY0Line(MultiRectArea mra, int[] line, int cx, int cy, int a) { + int prev = 0; + for(int i = 0; i < line.length; i++) { + mra.addRect(cx + (a - i), cy + prev, cx + (a - i), cy + line[i]); + prev = line[i] + 1; + } + } + + static void addY1Line(MultiRectArea mra, int[] line, int cx, int cy, int a) { + int prev = 0; + for(int i = 0; i < line.length; i++) { + mra.addRect(cx - (a - i), cy + prev, cx - (a - i), cy + line[i]); + prev = line[i] + 1; + } + } + + static void addY2Line(MultiRectArea mra, int[] line, int cx, int cy, int a) { + int prev = 0; + for(int i = 0; i < line.length; i++) { + mra.addRect(cx - (a - i), cy - line[i], cx - (a - i), cy - prev); + prev = line[i] + 1; + } + } + + static void addY3Line(MultiRectArea mra, int[] line, int cx, int cy, int a) { + int prev = 0; + for(int i = 0; i < line.length; i++) { + mra.addRect(cx + (a - i), cy - line[i], cx + (a - i), cy - prev); + prev = line[i] + 1; + } + } + + /** + * Returns normalized angle (from 0 to 360 degrees) + */ + static double getNormAngle(double angle) { + angle -= Math.floor(angle / 360) * 360; + if (angle < 0) { + angle += 360; + } + return angle; + } + + /** + * Creates arc lookup table + */ + static int[] createLine(int a, int b, int xcount, int ycount) { + int[] buf = new int[b - ycount + 1]; + int d = a * a + 2 * b * b - 2 * a * a * b; + int x = 0; + int y = b; + while (y >= ycount) { + if (d < 0) { + d = d + b * b * (4 * x + 6); + } else { + buf[b - y] = x; + d = d + b * b * (4 * x + 6) + 4 * a * a * (1 - y); + y--; + } + x++; + } + return buf; + } + + /** + * Adds head/tail arc segment to MultiRectArea + */ + static void addSeg(MultiRectArea mra, int cx1, int cy1, int cx2, int cy2, int a, int b, int[] xline, int[] yline, int[] bounds) { + switch(bounds[0]) { + case 0: + addY3LineSeg(mra, yline, cx2, cy1, a, bounds[1], bounds[2]); + break; + case 1: + addX1LineSeg(mra, xline, cx2, cy1, b, bounds[1], bounds[2]); + break; + case 2: + addX2LineSeg(mra, xline, cx1, cy1, b, bounds[1], bounds[2]); + break; + case 3: + addY2LineSeg(mra, yline, cx1, cy1, a, bounds[1], bounds[2]); + break; + case 4: + addY1LineSeg(mra, yline, cx1, cy2, a, bounds[1], bounds[2]); + break; + case 5: + addX3LineSeg(mra, xline, cx1, cy2, b, bounds[1], bounds[2]); + break; + case 6: + addX0LineSeg(mra, xline, cx2, cy2, b, bounds[1], bounds[2]); + break; + case 7: + addY0LineSeg(mra, yline, cx2, cy2, a, bounds[1], bounds[2]); + break; + } + } + + /** + * Returns bounds for non quadratic arc head + */ + static int[] getSegment1(double angle, int ax, int ay, int xcount, int ycount) { + int[] bounds = new int[3]; + switch((int)(angle / 90)) { + case 0: + if (xcount < ax) { + bounds[0] = 0; // Y3 + bounds[1] = -ay; + bounds[2] = ycount; + } else { + bounds[0] = 1; // X1 + bounds[1] = 0; + bounds[2] = ax; + } + break; + case 1: + if (xcount > -ax) { + bounds[0] = 2; // X2 + bounds[1] = -ax; + bounds[2] = xcount; + } else { + bounds[0] = 3; // Y2 + bounds[1] = 0; + bounds[2] = -ay; + } + break; + case 2: + if (xcount < -ax) { + bounds[0] = 4; // Y1 + bounds[1] = ay; + bounds[2] = ycount; + } else { + bounds[0] = 5; // X3 + bounds[1] = 0; + bounds[2] = -ax; + } + break; + case 3: + if (xcount > ax) { + bounds[0] = 6; // X0 + bounds[1] = ax; + bounds[2] = xcount; + } else { + bounds[0] = 7; // Y0 + bounds[1] = 0; + bounds[2] = ay; + } + break; + } + return bounds; + } + + /** + * Returns bounds for non quadratic arc tail + */ + static int[] getSegment2(double angle, int ax, int ay, int xcount, int ycount) { + int[] bounds = new int[3]; + switch((int)(angle / 90)) { + case 0: + if (xcount < ax) { + bounds[0] = 0; // Y3 + bounds[1] = 0; + bounds[2] = -ay; + } else { + bounds[0] = 1; // X1 + bounds[1] = ax; + bounds[2] = xcount; + } + break; + case 1: + if (xcount > -ax) { + bounds[0] = 2; // X2 + bounds[1] = 0; + bounds[2] = -ax; + } else { + bounds[0] = 3; // Y2 + bounds[1] = -ay; + bounds[2] = ycount; + } + break; + case 2: + if (xcount < -ax) { + bounds[0] = 4; // Y1 + bounds[1] = 0; + bounds[2] = ay; + } else { + bounds[0] = 5; // X3 + bounds[1] = -ax; + bounds[2] = xcount; + } + break; + case 3: + if (xcount > ax) { + bounds[0] = 6; // X0 + bounds[1] = 0; + bounds[2] = ax; + } else { + bounds[0] = 7; // Y0 + bounds[1] = ay; + bounds[2] = ycount; + } + break; + } + return bounds; + } + + /** + * Rasterizes arc using clippind and dashing style + * @param x1 - the x coordinate of the left-upper corner of the arc bounds + * @param y1 - the y coordinate of the left-upper corner of the arc bounds + * @param width - the width of the arc bounds + * @param height - the height of the arc bounds + * @param angleStart - the start angle of the arc in degrees + * @param angleExtent - the angle extent in degrees + * @param clip - the MultiRectArea object of clipping area + * @return a MultiRectArea of rasterizer arc + */ + public static MultiRectArea rasterize(int x, int y, int width, int height, double angleStart, double angleExtent, MultiRectArea clip) { + + MultiRectArea mra = new MultiRectArea(false); + + int cx1, cx2, cy1, cy2; + cx1 = cx2 = x + width / 2; + cy1 = cy2 = y + height / 2; + + if (width % 2 == 0) { + cx2--; + } + + if (height % 2 == 0) { + cy2--; + } + + int a = width / 2; + int b = height / 2; + double c = Math.sqrt(a * a + b * b); + + int xcount, ycount; + if (a < b) { + xcount = (int)Math.ceil(a * a / c); + ycount = (int)Math.floor(b * b / c); + } else { + xcount = (int)Math.floor(a * a / c); + ycount = (int)Math.ceil(b * b / c); + } + + int[] xline = createLine(a, b, xcount, ycount); + int[] yline = createLine(b, a, ycount, xcount); + + // Correct lines + int i = xline.length; + while(xline[--i] > xcount) { + xline[i] = xcount; + } + + i = yline.length; + while(yline[--i] > ycount) { + yline[i] = ycount; + } + + if (Math.abs(angleExtent) >= 360) { + // Rasterize CIRCLE + addX0Line(mra, xline, cx2, cy2, b); + addX1Line(mra, xline, cx2, cy1, b); + addX2Line(mra, xline, cx1, cy1, b); + addX3Line(mra, xline, cx1, cy2, b); + addY0Line(mra, yline, cx2, cy2, a); + addY1Line(mra, yline, cx1, cy2, a); + addY2Line(mra, yline, cx1, cy1, a); + addY3Line(mra, yline, cx2, cy1, a); + } else { + // Rasterize ARC + angleStart = getNormAngle(angleStart); + double angleFinish = getNormAngle(angleStart + angleExtent); + + if (angleExtent < 0) { + double tmp = angleStart; + angleStart = angleFinish; + angleFinish = tmp; + } + + double radStart = -Math.toRadians(angleStart); + double radFinish = -Math.toRadians(angleFinish); + int ax1 = (int)(a * Math.cos(radStart)); + int ay1 = (int)(b * Math.sin(radStart)); + int ax2 = (int)(a * Math.cos(radFinish)); + int ay2 = (int)(b * Math.sin(radFinish)); + + int[] seg1 = getSegment1(angleStart, ax1, ay1, xcount, ycount); + int[] seg2 = getSegment2(angleFinish, ax2, ay2, xcount, ycount); + + // Start and Finish located in the same quater + if (angleStart < angleFinish && seg1[0] == seg2[0]) { + if (seg1[0] % 2 == 0) { + seg1[2] = seg2[2]; + } else { + seg1[1] = seg2[1]; + } + addSeg(mra, cx1, cy1, cx2, cy2, a, b, xline, yline, seg1); + return mra; + } + + addSeg(mra, cx1, cy1, cx2, cy2, a, b, xline, yline, seg1); + addSeg(mra, cx1, cy1, cx2, cy2, a, b, xline, yline, seg2); + + int startSeg = (seg1[0] + 1) % 8; + int finishSeg = seg2[0]; + + while (startSeg != finishSeg) { + switch(startSeg) { + case 0: + addY3Line(mra, yline, cx2, cy1, a); + break; + case 1: + addX1Line(mra, xline, cx2, cy1, b); + break; + case 2: + addX2Line(mra, xline, cx1, cy1, b); + break; + case 3: + addY2Line(mra, yline, cx1, cy1, a); + break; + case 4: + addY1Line(mra, yline, cx1, cy2, a); + break; + case 5: + addX3Line(mra, xline, cx1, cy2, b); + break; + case 6: + addX0Line(mra, xline, cx2, cy2, b); + break; + case 7: + addY0Line(mra, yline, cx2, cy2, a); + break; + } + startSeg = (startSeg + 1) % 8; + } + } + + if (clip != null) { + mra.intersect(clip); + } + + return mra; + } + +} \ No newline at end of file diff --git a/awt/org/apache/harmony/awt/gl/render/JavaBlitter.java b/awt/org/apache/harmony/awt/gl/render/JavaBlitter.java new file mode 100644 index 0000000..67e0a59 --- /dev/null +++ b/awt/org/apache/harmony/awt/gl/render/JavaBlitter.java @@ -0,0 +1,611 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Igor V. Stolyarov + * @version $Revision$ + * Created on 18.11.2005 + * + */ +package org.apache.harmony.awt.gl.render; + +import java.awt.AlphaComposite; +import java.awt.Color; +import java.awt.Composite; +import java.awt.CompositeContext; +import java.awt.Rectangle; +import java.awt.geom.AffineTransform; +import java.awt.geom.NoninvertibleTransformException; +import java.awt.geom.Rectangle2D; +import java.awt.image.ColorModel; +import java.awt.image.Raster; +import java.awt.image.WritableRaster; + +import org.apache.harmony.awt.gl.MultiRectArea; +import org.apache.harmony.awt.gl.Surface; +import org.apache.harmony.awt.gl.XORComposite; +import org.apache.harmony.awt.internal.nls.Messages; + +/** + * Java implenetation of the Blitter interface. Using when we can't + * draw images natively. + */ +public class JavaBlitter implements Blitter { + + /** + * Instead of multiplication and division we are using values from + * Lookup tables. + */ + static byte mulLUT[][]; // Lookup table for multiplication + static byte divLUT[][]; // Lookup table for division + + static{ + mulLUT = new byte[256][256]; + for(int i = 0; i < 256; i++){ + for(int j = 0; j < 256; j++){ + mulLUT[i][j] = (byte)((float)(i * j)/255 + 0.5f); + } + } + divLUT = new byte[256][256]; + for(int i = 1; i < 256; i++){ + for(int j = 0; j < i; j++){ + divLUT[i][j] = (byte)(((float)j / 255) / ((float)i/ 255) * 255 + 0.5f); + } + for(int j = i; j < 256; j++){ + divLUT[i][j] = (byte)255; + } + } + } + + final static int AlphaCompositeMode = 1; + final static int XORMode = 2; + + final static JavaBlitter inst = new JavaBlitter(); + + public static JavaBlitter getInstance(){ + return inst; + } + + public void blit(int srcX, int srcY, Surface srcSurf, int dstX, int dstY, + Surface dstSurf, int width, int height, AffineTransform sysxform, + AffineTransform xform, Composite comp, Color bgcolor, + MultiRectArea clip) { + + if(xform == null){ + blit(srcX, srcY, srcSurf, dstX, dstY, dstSurf, width, height, + sysxform, comp, bgcolor, clip); + }else{ + double scaleX = xform.getScaleX(); + double scaleY = xform.getScaleY(); + double scaledX = dstX / scaleX; + double scaledY = dstY / scaleY; + AffineTransform at = new AffineTransform(); + at.setToTranslation(scaledX, scaledY); + xform.concatenate(at); + sysxform.concatenate(xform); + blit(srcX, srcY, srcSurf, 0, 0, dstSurf, width, height, + sysxform, comp, bgcolor, clip); + } + + } + + public void blit(int srcX, int srcY, Surface srcSurf, int dstX, int dstY, + Surface dstSurf, int width, int height, AffineTransform sysxform, + Composite comp, Color bgcolor, MultiRectArea clip) { + + if(sysxform == null) { + sysxform = new AffineTransform(); + } + int type = sysxform.getType(); + switch(type){ + case AffineTransform.TYPE_TRANSLATION: + dstX += sysxform.getTranslateX(); + dstY += sysxform.getTranslateY(); + case AffineTransform.TYPE_IDENTITY: + blit(srcX, srcY, srcSurf, dstX, dstY, dstSurf, + width, height, comp, bgcolor, clip); + break; + default: + int srcW = srcSurf.getWidth(); + int srcH = srcSurf.getHeight(); + + int w = srcX + width < srcW ? width : srcW - srcX; + int h = srcY + height < srcH ? height : srcH - srcY; + + ColorModel srcCM = srcSurf.getColorModel(); + Raster srcR = srcSurf.getRaster().createChild(srcX, srcY, + w, h, 0, 0, null); + + ColorModel dstCM = dstSurf.getColorModel(); + WritableRaster dstR = dstSurf.getRaster(); + + transformedBlit(srcCM, srcR, 0, 0, dstCM, dstR, dstX, dstY, w, h, + sysxform, comp, bgcolor, clip); + + } + } + + public void blit(int srcX, int srcY, Surface srcSurf, int dstX, int dstY, + Surface dstSurf, int width, int height, Composite comp, + Color bgcolor, MultiRectArea clip) { + + javaBlt(srcX, srcY, srcSurf.getWidth(), srcSurf.getHeight(), + srcSurf.getColorModel(), srcSurf.getRaster(), dstX, dstY, + dstSurf.getWidth(), dstSurf.getHeight(), + dstSurf.getColorModel(), dstSurf.getRaster(), + width, height, comp, bgcolor, clip); + + } + public void javaBlt(int srcX, int srcY, int srcW, int srcH, + ColorModel srcCM, Raster srcRast, int dstX, int dstY, + int dstW, int dstH, ColorModel dstCM, WritableRaster dstRast, + int width, int height, Composite comp, Color bgcolor, + MultiRectArea clip){ + + int srcX2 = srcW - 1; + int srcY2 = srcH - 1; + int dstX2 = dstW - 1; + int dstY2 = dstH - 1; + + if(srcX < 0){ + width += srcX; + srcX = 0; + } + if(srcY < 0){ + height += srcY; + srcY = 0; + } + + if(dstX < 0){ + width += dstX; + srcX -= dstX; + dstX = 0; + } + if(dstY < 0){ + height += dstY; + srcY -= dstY; + dstY = 0; + } + + if(srcX > srcX2 || srcY > srcY2) { + return; + } + if(dstX > dstX2 || dstY > dstY2) { + return; + } + + if(srcX + width > srcX2) { + width = srcX2 - srcX + 1; + } + if(srcY + height > srcY2) { + height = srcY2 - srcY + 1; + } + if(dstX + width > dstX2) { + width = dstX2 - dstX + 1; + } + if(dstY + height > dstY2) { + height = dstY2 - dstY + 1; + } + + if(width <= 0 || height <= 0) { + return; + } + + int clipRects[]; + if(clip != null) { + clipRects = clip.rect; + } else { + clipRects = new int[]{5, 0, 0, dstW - 1, dstH - 1}; + } + + boolean isAlphaComp = false; + int rule = 0; + float alpha = 0; + boolean isXORComp = false; + Color xorcolor = null; + CompositeContext cont = null; + + if(comp instanceof AlphaComposite){ + isAlphaComp = true; + AlphaComposite ac = (AlphaComposite) comp; + rule = ac.getRule(); + alpha = ac.getAlpha(); + }else if(comp instanceof XORComposite){ + isXORComp = true; + XORComposite xcomp = (XORComposite) comp; + xorcolor = xcomp.getXORColor(); + }else{ + cont = comp.createContext(srcCM, dstCM, null); + } + + for(int i = 1; i < clipRects[0]; i += 4){ + int _sx = srcX; + int _sy = srcY; + + int _dx = dstX; + int _dy = dstY; + + int _w = width; + int _h = height; + + int cx = clipRects[i]; // Clipping left top X + int cy = clipRects[i + 1]; // Clipping left top Y + int cx2 = clipRects[i + 2]; // Clipping right bottom X + int cy2 = clipRects[i + 3]; // Clipping right bottom Y + + if(_dx > cx2 || _dy > cy2 || dstX2 < cx || dstY2 < cy) { + continue; + } + + if(cx > _dx){ + int shx = cx - _dx; + _w -= shx; + _dx = cx; + _sx += shx; + } + + if(cy > _dy){ + int shy = cy - _dy; + _h -= shy; + _dy = cy; + _sy += shy; + } + + if(_dx + _w > cx2 + 1){ + _w = cx2 - _dx + 1; + } + + if(_dy + _h > cy2 + 1){ + _h = cy2 - _dy + 1; + } + + if(_sx > srcX2 || _sy > srcY2) { + continue; + } + + if(isAlphaComp){ + alphaCompose(_sx, _sy, srcCM, srcRast, _dx, _dy, + dstCM, dstRast, _w, _h, rule, alpha, bgcolor); + }else if(isXORComp){ + xorCompose(_sx, _sy, srcCM, srcRast, _dx, _dy, + dstCM, dstRast, _w, _h, xorcolor); + }else{ + Raster sr = srcRast.createChild(_sx, _sy, _w, _h, 0, 0, null); + WritableRaster dr = dstRast.createWritableChild(_dx, _dy, + _w, _h, 0, 0, null); + cont.compose(sr, dr, dr); + } + } + } + + void alphaCompose(int srcX, int srcY, ColorModel srcCM, Raster srcRast, + int dstX, int dstY, ColorModel dstCM, WritableRaster dstRast, + int width, int height, int rule, float alpha, Color bgcolor){ + + Object srcPixel, dstPixel; + int srcConstAllpha = (int)(alpha * 255 + 0.5f); + int srcRGB, dstRGB = 0; + + if(bgcolor != null){ + dstRGB = bgcolor.getRGB(); + } + + for(int sy = srcY, dy = dstY, srcYMax = srcY + height; sy < srcYMax; sy++, dy++){ + for(int sx = srcX, dx = dstX, srcXMax = srcX + width; sx < srcXMax; sx++, dx++){ + srcPixel = srcRast.getDataElements(sx, sy, null); + srcRGB = srcCM.getRGB(srcPixel); + if(bgcolor == null){ + dstPixel = dstRast.getDataElements(dx, dy, null); + dstRGB = dstCM.getRGB(dstPixel); + } + + dstRGB = compose(srcRGB, srcCM.isAlphaPremultiplied(), + dstRGB, dstCM.hasAlpha(), dstCM.isAlphaPremultiplied(), + rule, srcConstAllpha); + + dstPixel = dstCM.getDataElements(dstRGB, null); + dstRast.setDataElements(dx,dy,dstPixel); + } + } + } + + void xorCompose(int srcX, int srcY, ColorModel srcCM, Raster srcRast, + int dstX, int dstY, ColorModel dstCM, WritableRaster dstRast, + int width, int height, Color xorcolor){ + + Object srcPixel, dstPixel; + int xorRGB = xorcolor.getRGB(); + int srcRGB, dstRGB; + + for(int sy = srcY, dy = dstY, srcYMax = srcY + height; sy < srcYMax; sy++, dy++){ + for(int sx = srcX, dx = dstX, srcXMax = srcX + width; sx < srcXMax; sx++, dx++){ + srcPixel = srcRast.getDataElements(sx, sy, null); + dstPixel = dstRast.getDataElements(dx, dy, null); + + srcRGB = srcCM.getRGB(srcPixel); + dstRGB = dstCM.getRGB(dstPixel); + dstRGB = srcRGB ^ xorRGB ^ dstRGB; + + dstRGB = 0xff000000 | dstRGB; + dstPixel = dstCM.getDataElements(dstRGB, dstPixel); + dstRast.setDataElements(dx,dy,dstPixel); + + } + } + + } + + private void transformedBlit(ColorModel srcCM, Raster srcR, int srcX, int srcY, + ColorModel dstCM, WritableRaster dstR, int dstX, int dstY, + int width, int height, AffineTransform at, Composite comp, + Color bgcolor,MultiRectArea clip) { + + Rectangle srcBounds = new Rectangle(width, height); + Rectangle dstBlitBounds = new Rectangle(dstX, dstY, srcR.getWidth(), srcR.getHeight()); + + Rectangle transSrcBounds = getBounds2D(at, srcBounds).getBounds(); + Rectangle transDstBlitBounds = getBounds2D(at, dstBlitBounds).getBounds(); + + int translateX = transDstBlitBounds.x - transSrcBounds.x; + int translateY = transDstBlitBounds.y - transSrcBounds.y; + + AffineTransform inv = null; + try { + inv = at.createInverse(); + } catch (NoninvertibleTransformException e) { + return; + } + + double[] m = new double[6]; + inv.getMatrix(m); + + int clipRects[]; + if(clip != null) { + clipRects = clip.rect; + } else { + clipRects = new int[]{5, 0, 0, dstR.getWidth(), dstR.getHeight()}; + } + + int compType = 0; + int srcConstAlpha = 0; + int rule = 0; + int bgRGB = bgcolor == null ? 0 : bgcolor.getRGB(); + int srcRGB = 0, dstRGB = 0; + Object srcVal = null, dstVal = null; + if(comp instanceof AlphaComposite){ + compType = AlphaCompositeMode; + AlphaComposite ac = (AlphaComposite) comp; + rule = ac.getRule(); + srcConstAlpha = (int)(ac.getAlpha() * 255 + 0.5f); + }else if(comp instanceof XORComposite){ + compType = XORMode; + XORComposite xor = (XORComposite) comp; + bgRGB = xor.getXORColor().getRGB(); + } + + for(int i = 1; i < clipRects[0]; i += 4){ + Rectangle dstBounds = new Rectangle(clipRects[i], clipRects[i + 1], 0, 0); + dstBounds.add(clipRects[i + 2] + 1, clipRects[i + 1]); + dstBounds.add(clipRects[i + 2] + 1, clipRects[i + 3] + 1); + dstBounds.add(clipRects[i], clipRects[i + 3] + 1); + + Rectangle bounds = dstBounds.intersection(transDstBlitBounds); + + int minSrcX = srcBounds.x; + int minSrcY = srcBounds.y; + int maxSrcX = minSrcX + srcBounds.width; + int maxSrcY = minSrcY + srcBounds.height; + + int minX = bounds.x; + int minY = bounds.y; + int maxX = minX + bounds.width; + int maxY = minY + bounds.height; + + int hx = (int)((m[0] * 256) + 0.5); + int hy = (int)((m[1] * 256) + 0.5); + int vx = (int)((m[2] * 256) + 0.5); + int vy = (int)((m[3] * 256) + 0.5); + int sx = (int)((m[4] + m[0] * (bounds.x - translateX) + m[2] * (bounds.y - translateY)) * 256 + 0.5); + int sy = (int)((m[5] + m[1] * (bounds.x - translateX) + m[3] * (bounds.y - translateY)) * 256 + 0.5); + + vx -= hx * bounds.width; + vy -= hy * bounds.width; + + for(int y = minY; y < maxY; y++) { + for(int x = minX; x < maxX; x++) { + int px = sx >> 8; + int py = sy >> 8; + if (px >= minSrcX && py >= minSrcY && px < maxSrcX && py < maxSrcY) { + switch(compType){ + case AlphaCompositeMode: + srcVal = srcR.getDataElements(px , py , null); + srcRGB = srcCM.getRGB(srcVal); + if(bgcolor != null){ + dstRGB = bgRGB; + }else{ + dstVal = dstR.getDataElements(x, y, null); + dstRGB = dstCM.getRGB(dstVal); + } + dstRGB = compose(srcRGB, srcCM.isAlphaPremultiplied(), + dstRGB, dstCM.hasAlpha(), dstCM.isAlphaPremultiplied(), + rule, srcConstAlpha); + dstVal = dstCM.getDataElements(dstRGB, null); + dstR.setDataElements(x, y, dstVal); + break; + + case XORMode: + srcVal = srcR.getDataElements(px , py , null); + srcRGB = srcCM.getRGB(srcVal); + dstVal = dstR.getDataElements(x, y, null); + dstRGB = dstCM.getRGB(dstVal); + dstRGB = srcRGB ^ bgRGB; + + dstRGB = 0xff000000 | dstRGB; + dstVal = dstCM.getDataElements(dstRGB, null); + dstR.setDataElements(x, y, dstVal); + break; + + default: + // awt.37=Unknown composite type {0} + throw new IllegalArgumentException(Messages.getString("awt.37", //$NON-NLS-1$ + comp.getClass())); + } + } + sx += hx; + sy += hy; + } + sx += vx; + sy += vy; + } + } + + } + + private Rectangle2D getBounds2D(AffineTransform at, Rectangle r) { + int x = r.x; + int y = r.y; + int width = r.width; + int height = r.height; + + float[] corners = { + x, y, + x + width, y, + x + width, y + height, + x, y + height + }; + + at.transform(corners, 0, corners, 0, 4); + + Rectangle2D.Float bounds = new Rectangle2D.Float(corners[0], corners[1], 0 , 0); + bounds.add(corners[2], corners[3]); + bounds.add(corners[4], corners[5]); + bounds.add(corners[6], corners[7]); + + return bounds; + } + + private int compose(int srcRGB, boolean isSrcAlphaPre, + int dstRGB, boolean dstHasAlpha, boolean isDstAlphaPre, + int rule, int srcConstAlpha){ + + int sa, sr, sg, sb, da, dr, dg, db; + + sa = (srcRGB >> 24) & 0xff; + sr = (srcRGB >> 16) & 0xff; + sg = (srcRGB >> 8) & 0xff; + sb = srcRGB & 0xff; + + if(isSrcAlphaPre){ + sa = mulLUT[srcConstAlpha][sa] & 0xff; + sr = mulLUT[srcConstAlpha][sr] & 0xff; + sg = mulLUT[srcConstAlpha][sg] & 0xff; + sb = mulLUT[srcConstAlpha][sb] & 0xff; + }else{ + sa = mulLUT[srcConstAlpha][sa] & 0xff; + sr = mulLUT[sa][sr] & 0xff; + sg = mulLUT[sa][sg] & 0xff; + sb = mulLUT[sa][sb] & 0xff; + } + + da = (dstRGB >> 24) & 0xff; + dr = (dstRGB >> 16) & 0xff; + dg = (dstRGB >> 8) & 0xff; + db = dstRGB & 0xff; + + if(!isDstAlphaPre){ + dr = mulLUT[da][dr] & 0xff; + dg = mulLUT[da][dg] & 0xff; + db = mulLUT[da][db] & 0xff; + } + + int Fs = 0; + int Fd = 0; + switch(rule){ + case AlphaComposite.CLEAR: + break; + + case AlphaComposite.DST: + Fd = 255; + break; + + case AlphaComposite.DST_ATOP: + Fs = 255 - da; + Fd = sa; + break; + + case AlphaComposite.DST_IN: + Fd = sa; + break; + + case AlphaComposite.DST_OUT: + Fd = 255 - sa; + break; + + case AlphaComposite.DST_OVER: + Fs = 255 - da; + Fd = 255; + break; + + case AlphaComposite.SRC: + Fs = 255; + break; + + case AlphaComposite.SRC_ATOP: + Fs = da; + Fd = 255 - sa; + break; + + case AlphaComposite.SRC_IN: + Fs = da; + break; + + case AlphaComposite.SRC_OUT: + Fs = 255 - da; + break; + + case AlphaComposite.SRC_OVER: + Fs = 255; + Fd = 255 - sa; + break; + + case AlphaComposite.XOR: + Fs = 255 - da; + Fd = 255 - sa; + break; + } + dr = (mulLUT[sr][Fs] & 0xff) + (mulLUT[dr][Fd] & 0xff); + dg = (mulLUT[sg][Fs] & 0xff) + (mulLUT[dg][Fd] & 0xff); + db = (mulLUT[sb][Fs] & 0xff) + (mulLUT[db][Fd] & 0xff); + + da = (mulLUT[sa][Fs] & 0xff) + (mulLUT[da][Fd] & 0xff); + + if(!isDstAlphaPre){ + if(da != 255){ + dr = divLUT[da][dr] & 0xff; + dg = divLUT[da][dg] & 0xff; + db = divLUT[da][db] & 0xff; + } + } + if(!dstHasAlpha) { + da = 0xff; + } + dstRGB = (da << 24) | (dr << 16) | (dg << 8) | db; + + return dstRGB; + + } + +} diff --git a/awt/org/apache/harmony/awt/gl/render/JavaLineRasterizer.java b/awt/org/apache/harmony/awt/gl/render/JavaLineRasterizer.java new file mode 100644 index 0000000..eb6f7b5 --- /dev/null +++ b/awt/org/apache/harmony/awt/gl/render/JavaLineRasterizer.java @@ -0,0 +1,760 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Denis M. Kishenko + * @version $Revision$ + */ +package org.apache.harmony.awt.gl.render; + +import org.apache.harmony.awt.gl.MultiRectArea; + + +public class JavaLineRasterizer { + + /** + * LineDasher class provides dashing for particular dash style + */ + public static class LineDasher { + + int index; + float pos; + float phase; + float dash[]; + float inv[]; + boolean visible; + + public LineDasher() { + } + + public LineDasher(float dash[], float phase) { + this.dash = dash; + this.phase = phase; + + inv = new float[dash.length]; + int j = dash.length; + for (float element : dash) { + inv[--j] = element; + } + index = 0; + while (phase > dash[index]) { + phase -= dash[index]; + index = (index + 1) % dash.length; + } + visible = index % 2 == 0; + } + + void move(float step) { // main dasher + pos += step; + step += phase; + while(step >= dash[index]) { + step -= dash[index]; + index = (index + 1) % dash.length; + visible = !visible; + } + phase = step; + } + + float nextDash() { + phase = 0.0f; + index = (index + 1) % dash.length; + visible = !visible; + return dash[index]; + } + + LineDasher createDiagonal(double k, float length, boolean invert) { + LineDasher local = new LineDasher(); + local.dash = new float[dash.length]; + if (invert) { // inverted dasher + move(length); + local.phase = (float)((dash[index] - phase) * k); + local.visible = visible; + local.index = inv.length - index - 1; + for(int i = 0; i < inv.length; i++) { + local.dash[i] = (float)(inv[i] * k); + } + } else { + local.phase = (float)(phase * k); + local.visible = visible; + local.index = index; + for(int i = 0; i < dash.length; i++) { + local.dash[i] = (float)(dash[i] * k); + } + move(length); + } + return local; + } + + LineDasher createOrtogonal(float length, boolean invert) { + LineDasher local = new LineDasher(); + local.dash = new float[dash.length]; + if (invert) { // inverted dasher + move(length); + local.phase = dash[index] - phase; + local.visible = visible; + local.index = inv.length - index - 1; + local.dash = inv; + } else { + local.phase = phase; + local.visible = visible; + local.index = index; + local.dash = dash; + move(length); + } + return local; + } + + LineDasher createChild(float start) { + LineDasher child = new LineDasher(); + child.phase = phase; + child.visible = visible; + child.index = index; + child.dash = dash; + child.move(start); + return child; + } + + } + + /** + * Line class provides rasterization for different line types + */ + abstract static class Line { + + int x1, y1, x2, y2; + int x, y; + MultiRectArea dst; + + Line(int x1, int y1, int x2, int y2, MultiRectArea dst) { + this.x1 = x1; + this.y1 = y1; + this.x2 = x2; + this.y2 = y2; + this.dst = dst; + } + + static abstract class Diag extends Line { + int dx, dy, adx, ady, sx, sy; + int eBase, ePos, eNeg; + int xcount; + int e; + + Diag(int x1, int y1, int x2, int y2, MultiRectArea dst) { + super(x1, y1, x2, y2, dst); + dx = x2 - x1; + dy = y2 - y1; + sy = 1; + if (dx > 0) { + adx = dx; + sx = 1; + } else { + adx = -dx; + sx = -1; + } + ady = dy; + } + + float getLength() { + return (float)Math.sqrt(dx * dx + dy * dy); + } + + static class Hor extends Diag { + + Hor(int x1, int y1, int x2, int y2, MultiRectArea dst) { + super(x1, y1, x2, y2, dst); + eBase = ady + ady - adx; + ePos = 2 * (ady - adx); + eNeg = ady + ady; + xcount = adx; + } + + @Override + void rasterize() { + e = eBase; + x = x1; + y = y1; + rasterize(xcount); + } + + @Override + void rasterizeClipped(int nx1, int ny1, int nx2, int ny2) { + e = eBase + 2 * (ady * Math.abs(nx1 - x1) - adx * Math.abs(ny1 - y1)); + x = nx1; + y = ny1; + rasterize(dx > 0 ? nx2 - nx1 : nx1 - nx2); + } + + @Override + void rasterize(int count) { + int px = x; + while (count-- > 0) { + if (e >= 0) { + if (sx > 0) { + dst.addRect(px, y, x, y); + } else { + dst.addRect(x, y, px, y); + } + x += sx; + y += sy; + e += ePos; + px = x; + } else { + e += eNeg; + x += sx; + } + } + if (sx > 0) { + dst.addRect(px, y, x, y); + } else { + dst.addRect(x, y, px, y); + } + } + + @Override + void skip(int count) { + while (count-- > 0) { + x += sx; + if (e >= 0) { + y += sy; + e += ePos; + } else { + e += eNeg; + } + } + } + + } + + static class Ver extends Diag { + + Ver(int x1, int y1, int x2, int y2, MultiRectArea dst) { + super(x1, y1, x2, y2, dst); + eBase = adx + adx - ady; + ePos = 2 * (adx - ady); + eNeg = adx + adx; + xcount = ady; + } + + @Override + void rasterize() { + e = eBase; + x = x1; + y = y1; + rasterize(xcount); + } + + @Override + void rasterizeClipped(int nx1, int ny1, int nx2, int ny2) { + e = eBase + 2 * (adx * Math.abs(ny1 - y1) - ady * Math.abs(nx1 - x1)); + x = nx1; + y = ny1; + rasterize(ny2 - ny1); + } + + @Override + void rasterize(int count) { + int py = y; + while (count-- > 0) { + if (e >= 0) { + dst.addRect(x, py, x, y); + x += sx; + y += sy; + e += ePos; + py = y; + } else { + y += sy; + e += eNeg; + } + } + dst.addRect(x, py, x, y); + } + + @Override + void skip(int count) { + while (count-- > 0) { + y += sy; + if (e >= 0) { + x += sx; + e += ePos; + } else { + e += eNeg; + } + } + } + + } + + static class HorDashed extends Hor { + + LineDasher local; + + HorDashed(int x1, int y1, int x2, int y2, MultiRectArea dst, LineDasher dasher, boolean invert) { + super(x1, y1, x2, y2, dst); + float length = getLength(); + local = dasher.createDiagonal(xcount / length, length, invert); + } + + @Override + void rasterize() { + e = eBase; + x = x1; + y = y1; + rasterizeDash(xcount, local); + } + + @Override + void rasterizeClipped(int nx1, int ny1, int nx2, int ny2) { + e = eBase + 2 * (ady * Math.abs(nx1 - x1) - adx * Math.abs(ny1 - y1)); + x = nx1; + y = ny1; + rasterizeDash(Math.abs(nx2 - nx1), local.createChild(Math.abs(nx1 - x1))); + } + + } + + static class VerDashed extends Ver { + + LineDasher local; + + VerDashed(int x1, int y1, int x2, int y2, MultiRectArea dst, LineDasher dasher, boolean invert) { + super(x1, y1, x2, y2, dst); + float length = getLength(); + local = dasher.createDiagonal(xcount / length, length, invert); + } + + @Override + void rasterize() { + e = eBase; + x = x1; + y = y1; + rasterizeDash(xcount, local); + } + + @Override + void rasterizeClipped(int nx1, int ny1, int nx2, int ny2) { + e = eBase + 2 * (adx * Math.abs(ny1 - y1) - ady * Math.abs(nx1 - x1)); + x = nx1; + y = ny1; + rasterizeDash(ny2 - ny1, local.createChild(ny1 - y1)); + } + + } + + @Override + void rasterize(int[] clip, int index) { + int cx1 = clip[index + 0]; + int cy1 = clip[index + 1]; + int cx2 = clip[index + 2] + 1; + int cy2 = clip[index + 3] + 1; + + int code1 = + (x1 < cx1 ? 1 : 0) | (x1 >= cx2 ? 2 : 0) | + (y1 < cy1 ? 8 : 0) | (y1 >= cy2 ? 4 : 0); + int code2 = + (x2 < cx1 ? 1 : 0) | (x2 >= cx2 ? 2 : 0) | + (y2 < cy1 ? 8 : 0) | (y2 >= cy2 ? 4 : 0); + + // Outside + if ((code1 & code2) != 0) { + return; + } + + // Inside + if (code1 == 0 && code2 == 0) { + rasterize(); + return; + } + + // Clip + int nx1 = x1; + int ny1 = y1; + int nx2 = x2; + int ny2 = y2; + // need to clip + cx1 -= x1; cx2 -= x1; + cy1 -= y1; cy2 -= y1; +// int d; + int newx1 = 0, newy1 = 0, newx2 = 0, newy2 = 0; + if (code1 != 0) { + newx1 = Integer.MAX_VALUE; + if ((code1 & 8) != 0) { + // clip point 1 with top clip bound + newy1 = cy1; + newx1 = clipY(dx, dy, newy1, true); + + } else if ((code1 & 4) != 0) { + // clip point 1 with bottom clip bound + newy1 = cy2 - 1; + newx1 = clipY(dx, dy, newy1, false); + } + if ((code1 & 1) != 0 && (cx1 > newx1 || newx1 == Integer.MAX_VALUE)) { + // clip point 1 with left clip bound + newx1 = cx1; + newy1 = clipX(dx, dy, newx1, false); + } else if ((code1 & 2) != 0 && (newx1 >= cx2 || newx1 == Integer.MAX_VALUE)) { + // clip point 1 with right clip bound + newx1 = cx2 - 1; + newy1 = clipX(dx, dy, newx1, false); + } + if (newx1 < cx1 || newx1 >= cx2 || newy1 < cy1 || newy1 >= cy2) { + return; + } +// d = 2 * (ady * Math.abs(newx1) - adx * Math.abs(newy1)) + 2 * ady - adx; + } else { +// d = (ady << 1) - adx; + } + + if (code2 != 0) { + newx2=Integer.MAX_VALUE; + if ((code2 & 8) != 0) { + // clip point 2 with top clip bound + newy2 = cy1; + newx2 = clipY(dx, dy, newy2, true); + } else if ((code2 & 4) != 0) { + // clip point 2 with bottom clip bound + newy2 = cy2 - 1; + newx2 = clipY(dx, dy, newy2, false); + } + if ((code2 & 1) != 0 && (cx1 > newx2 || newx2 == Integer.MAX_VALUE)) { + // clip point 2 with left clip bound + newx2 = cx1; + newy2 = clipX(dx, dy, newx2, false); + } else if ((code2 & 2) != 0 && (newx2 >= cx2 || newx2 == Integer.MAX_VALUE)) { + // clip point 2 with right clip bound + newx2 = cx2 - 1; + newy2 = clipX(dx, dy, newx2, false); + } + if (newx2 < cx1 || newx2 >= cx2 || newy2 < cy1 || newy2 >= cy2) { + return; + } + nx2 = x1 + newx2; + ny2 = y1 + newy2; + } + nx1 = x1 + newx1; + ny1 = y1 + newy1; + + rasterizeClipped(nx1, ny1, nx2, ny2); + } + + abstract void rasterizeClipped(int nx1, int ny1, int nx2, int ny2); + + } + + static abstract class Ortog extends Line { + + Ortog(int x1, int y1, int x2, int y2, MultiRectArea dst) { + super(x1, y1, x2, y2, dst); + } + + static class Hor extends Ortog { + + int dx; + + Hor(int x1, int y1, int x2, int y2, MultiRectArea dst) { + super(x1, y1, x2, y2, dst); + dx = x2 - x1; + } + + @Override + void rasterize() { + if (dx > 0) { + dst.addRect(x1, y1, x2, y2); + } else { + dst.addRect(x2, y2, x1, y1); + } + } + + @Override + void rasterize(int step) { + int px = x; + if (dx > 0) { + x += step; + dst.addRect(px, y1, x - 1, y2); + } else { + x -= step; + dst.addRect(x + 1, y2, px, y1); + } + } + + @Override + void skip(int step) { + if (dx > 0) { + x += step; + } else { + x -= step; + } + } + + void rasterizeClipped(int nx1, int nx2) { + if (nx1 < nx2) { + dst.addRect(nx1, y1, nx2, y1); + } else { + dst.addRect(nx2, y1, nx1, y1); + } + } + + @Override + void rasterize(int[] clip, int index) { + if (y1 >= clip[index + 1] && y1 <= clip[index + 3]) { + int cx1 = clip[index + 0]; + int cx2 = clip[index + 2]; + if (x1 <= cx2 && x2 >= cx1) { + int nx1, nx2; + if (dx > 0) { + nx1 = Math.max(x1, cx1); + nx2 = Math.min(x2, cx2); + } else { + nx2 = Math.max(x2, cx1); + nx1 = Math.min(x1, cx2); + } + rasterizeClipped(nx1, nx2); + } + } + } + + } + + static class Ver extends Ortog { + + int dy; + + Ver(int x1, int y1, int x2, int y2, MultiRectArea dst) { + super(x1, y1, x2, y2, dst); + dy = y2 - y1; + } + + @Override + void rasterize() { + dst.addRect(x1, y1, x2, y2); + } + + @Override + void rasterize(int step) { + int py = y; + y += step; + dst.addRect(x1, py, x2, y - 1); + } + + @Override + void skip(int step) { + y += step; + } + + void rasterizeClipped(int ny1, int ny2) { + dst.addRect(x1, ny1, x1, ny2); + } + + @Override + void rasterize(int[] clip, int index) { + if (x1 >= clip[index] && x1 <= clip[index + 2]) { + int cy1 = clip[index + 1]; + int cy2 = clip[index + 3]; + if (y1 <= cy2 && y2 >= cy1) { + rasterizeClipped(Math.max(y1, cy1), Math.min(y2, cy2)); + } + } + } + + } + + static class HorDashed extends Hor { + + LineDasher local; + + HorDashed(int x1, int y1, int x2, int y2, MultiRectArea dst, LineDasher dasher) { + super(x1, y1, x2, y2, dst); + dx = x2 - x1; + local = dasher.createOrtogonal(Math.abs(dx), false); + } + + @Override + void rasterize() { + x = x1; + y = y1; + rasterizeDash(Math.abs(dx), local); + } + + @Override + void rasterizeClipped(int nx1, int nx2) { + x = nx1; + y = y1; + rasterizeDash(Math.abs(nx2 - nx1), local.createChild(Math.abs(nx1 - x1))); + } + + } + + static class VerDashed extends Ver { + + LineDasher local; + + VerDashed(int x1, int y1, int x2, int y2, MultiRectArea dst, LineDasher dasher, boolean invert) { + super(x1, y1, x2, y2, dst); + dy = y2 - y1; + local = dasher.createOrtogonal(dy, invert); + } + + @Override + void rasterize() { + x = x1; + y = y1; + rasterizeDash(dy, local); + } + + @Override + void rasterizeClipped(int ny1, int ny2) { + x = x1; + y = ny1; + rasterizeDash(ny2 - ny1, local.createChild(ny1)); + } + + } + + } + + abstract void rasterize(); + abstract void rasterize(int[] clip, int index); + abstract void rasterize(int count); + abstract void skip(int count); + + void rasterizeDash(int count, LineDasher dasher) { + float delta = dasher.dash[dasher.index] - dasher.phase; + int step = (int)delta; + delta -= step; + while(count > step) { + if (dasher.visible) { + rasterize(step); + } else { + skip(step); + } + count -= step; + delta += dasher.nextDash(); + step = (int)delta; + delta -= step; + } + if (count > 0 && dasher.visible) { + rasterize(count); + dasher.move(count); + } + } + + } + + /** + * Common clipping method + */ + static int clip(int dX1, int dX2, int cX, boolean top) { + int adX1 = dX1 < 0 ? -dX1 : dX1; + int adX2 = dX2 < 0 ? -dX2 : dX2; + if (adX1 <= adX2) { + // obtuse intersection angle + return ((dX1 << 1) * cX + (dX1 > 0 ? dX2 : -dX2)) / (dX2 << 1); + } + int k; + if (top) { + k = -dX1 + (dX2 < 0 ? 0 : dX1 > 0 ? (dX2 << 1) : -(dX2 << 1)); + } else { + k = dX1 + (dX2 > 0 ? 0 : dX1 > 0 ? (dX2 << 1) : -(dX2 << 1)); + } + + k += dX1 > 0 == dX2 > 0 ? -1 : 1; + return ((dX1 << 1) * cX + k) / (dX2 << 1); + } + + /** + * Clipping along X axis + */ + static int clipX(int dx, int dy, int cy, boolean top) { + return clip(dy, dx, cy, top); + } + + /** + * Clipping along Y axis + */ + static int clipY(int dx, int dy, int cx, boolean top) { + return clip(dx, dy, cx, top); + } + + /** + * Rasterizes line using clippind and dashing style + * @param x1 - the x coordinate of the first control point + * @param y1 - the y coordinate of the first control point + * @param x2 - the x coordinate of the second control point + * @param y2 - the y coordinate of the second control point + * @param clip - the MultiRectArea object of clipping area + * @param dasher - the dasher style + * @param invert - the invert indicator, always false + * @return a MultiRectArea of rasterizer line + */ + public static MultiRectArea rasterize(int x1, int y1, int x2, int y2, MultiRectArea clip, LineDasher dasher, boolean invert) { + + MultiRectArea dst = new MultiRectArea(false); + int dx = x2 - x1; + int dy = y2 - y1; + + // Point + if (dx == 0 && dy == 0) { + if ((clip == null || clip.contains(x1, y1)) && (dasher == null || dasher.visible)) { + dst = new MultiRectArea(x1, y1, x1, y1); + } + return dst; + } + + if (dy < 0) { + return rasterize(x2, y2, x1, y1, clip, dasher, true); + } + + Line line; + if (dasher == null) { + if (dx == 0) { + line = new Line.Ortog.Ver(x1, y1, x2, y2, dst); + } else + if (dy == 0) { + line = new Line.Ortog.Hor(x1, y1, x2, y2, dst); + } else { + if (dy < Math.abs(dx)) { + line = new Line.Diag.Hor(x1, y1, x2, y2, dst); + } else { + line = new Line.Diag.Ver(x1, y1, x2, y2, dst); + } + } + } else { + if (dx == 0) { + line = new Line.Ortog.VerDashed(x1, y1, x2, y2, dst, dasher, invert); + } else + if (dy == 0) { + line = new Line.Ortog.HorDashed(x1, y1, x2, y2, dst, dasher); + } else { + if (dy < Math.abs(dx)) { + line = new Line.Diag.HorDashed(x1, y1, x2, y2, dst, dasher, invert); + } else { + line = new Line.Diag.VerDashed(x1, y1, x2, y2, dst, dasher, invert); + } + } + } + + + if (clip == null || clip.isEmpty()) { + line.rasterize(); + } else { + for(int i = 1; i < clip.rect[0]; i += 4) { + line.rasterize(clip.rect, i); + } + } + + return dst; + } + +} diff --git a/awt/org/apache/harmony/awt/gl/render/JavaShapeRasterizer.java b/awt/org/apache/harmony/awt/gl/render/JavaShapeRasterizer.java new file mode 100644 index 0000000..dbaaf53 --- /dev/null +++ b/awt/org/apache/harmony/awt/gl/render/JavaShapeRasterizer.java @@ -0,0 +1,475 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Denis M. Kishenko + * @version $Revision$ + */ +package org.apache.harmony.awt.gl.render; + +import java.awt.Shape; +import java.awt.geom.PathIterator; + +import org.apache.harmony.awt.gl.MultiRectArea; +import org.apache.harmony.awt.internal.nls.Messages; + +public class JavaShapeRasterizer { + + static final int POINT_CAPACITY = 16; + + int edgesCount; + int edgeCur; + int[] edgesX; + int[] edgesY; + int[] edgesYS; // Y coordinate of edge START point + int[] edgesN; + int[] edgesDY; + int[] bounds; + int boundCount; + boolean[] edgesExt; // Extremal points + + int activeCount; + float[] activeX; + int[] activeYEnd; + float[] activeXStep; + int[] activeDY; + boolean[] activeExt; + + int[] crossX; + int[] crossDY; + + Filler filler; + + /** + * Rasterization filler for different path rules + */ + static abstract class Filler { + + static class NonZero extends Filler { + @Override + void add(MultiRectArea.LineCash rect, int[] points, int[] orient, int length, int y) { + + int[] dst = new int[length]; + int dstLength = 1; + dst[0] = points[0]; + int count = 0; + boolean inside = true; + for(int i = 0; i < length; i++) { + count += orient[i] > 0 ? 1 : -1; + if (count == 0) { + dst[dstLength++] = points[i]; + inside = false; + } else { + if (!inside) { + dst[dstLength++] = points[i]; + inside = true; + } + } + + } + + for(int i = 1; i < dstLength; i += 2) { + dst[i]--; + } + + dstLength = excludeEmpty(dst, dstLength); +// System.out.println("test"); + + dstLength = union(dst, dstLength); + + rect.addLine(dst, dstLength); + } + } + + static class EvenOdd extends Filler { + @Override + void add(MultiRectArea.LineCash rect, int[] points, int[] orient, int length, int y) { + /* + int[] buf = new int[length]; + int j = 0; + for(int i = 0; i < length - 1; i++) { + if (points[i] != points[i + 1]) { + buf[j++] = points[i]; + } + } + */ + for(int i = 1; i < length; i += 2) { + points[i]--; + } + + length = excludeEmpty(points, length); +// System.out.println("test"); + + length = union(points, length); + rect.addLine(points, length); + /* + for(int i = 0; i < length;) { + rect.add(points[i++], y, points[i++], y); + } + */ + } + } + + abstract void add(MultiRectArea.LineCash rect, int[] points, int[] orient, int length, int y); + + static int excludeEmpty(int[] points, int length) { + int i = 0; + while(i < length) { + if (points[i] <= points[i + 1]) { + i += 2; + } else { + length -= 2; + System.arraycopy(points, i + 2, points, i, length - i); + } + } + return length; + } + + static int union(int[] points, int length) { + int i = 1; + while(i < length - 1) { + if (points[i] < points[i - 1]) { + System.arraycopy(points, i + 1, points, i - 1, length - i - 1); + length -= 2; + } else + if (points[i] >= points[i + 1] - 1) { + System.arraycopy(points, i + 2, points, i, length - i - 2); + length -= 2; + } else { + i += 2; + } + } + return length; + } + + } + + public JavaShapeRasterizer() { + } + + /** + * Checks buffer size and realloc if necessary + */ + int[] checkBufSize(int[] buf, int size) { + if (size == buf.length) { + int[] tmp; + tmp = new int[size + POINT_CAPACITY]; + System.arraycopy(buf, 0, tmp, 0, buf.length); + buf = tmp; + } + return buf; + } + + /** + * Adds to the buffers new edge + */ + void addEdge(int x, int y, int num) { + edgesX = checkBufSize(edgesX, edgesCount); + edgesY = checkBufSize(edgesY, edgesCount); + edgesN = checkBufSize(edgesN, edgesCount); + edgesX[edgesCount] = x; + edgesY[edgesCount] = y; + edgesN[edgesCount] = (num << 16) | edgesCount; + edgesCount++; + } + + /** + * Prepare all buffers and variable to rasterize shape + */ + void makeBuffer(PathIterator path, double flatness) { + edgesX = new int[POINT_CAPACITY]; + edgesY = new int[POINT_CAPACITY]; + edgesN = new int[POINT_CAPACITY]; + bounds = new int[POINT_CAPACITY]; + boundCount = 0; + edgesCount = 0; + + if (path.getWindingRule() == PathIterator.WIND_EVEN_ODD) { + filler = new Filler.EvenOdd(); + } else { + filler = new Filler.NonZero(); + } + float[] coords = new float[2]; + boolean closed = true; + while (!path.isDone()) { + switch(path.currentSegment(coords)) { + case PathIterator.SEG_MOVETO: + if (!closed) { + boundCount++; + bounds = checkBufSize(bounds, boundCount); + bounds[boundCount] = edgesCount; + } + addEdge((int)coords[0], (int)coords[1], boundCount); + closed = false; + break; + case PathIterator.SEG_LINETO: + addEdge((int)coords[0], (int)coords[1], boundCount); + break; + case PathIterator.SEG_CLOSE: + boundCount++; + bounds = checkBufSize(bounds, boundCount); + bounds[boundCount] = edgesCount; + closed = true; + break; + default: + // awt.36=Wrong segment + throw new RuntimeException(Messages.getString("awt.36")); //$NON-NLS-1$ + } + path.next(); + } + if (!closed) { + boundCount++; + bounds = checkBufSize(bounds, boundCount); + bounds[boundCount] = edgesCount; + } + } + + /** + * Sort buffers + */ + void sort(int[] master, int[] slave, int length) { + for(int i = 0; i < length - 1; i++) { + int num = i; + int min = master[num]; + for(int j = i + 1; j < length; j++) { + if (master[j] < min) { + num = j; + min = master[num]; + } + } + if (num != i) { + master[num] = master[i]; + master[i] = min; + min = slave[num]; + slave[num] = slave[i]; + slave[i] = min; + } + } + } + + int getNext(int cur) { + int n = edgesN[cur]; + int bound = n >> 16; + int num = (n & 0xFFFF) + 1; + if (num == bounds[bound + 1]) { + return bounds[bound]; + } + return num; + } + + int getPrev(int cur) { + int n = edgesN[cur]; + int bound = n >> 16; + int num = (n & 0xFFFF) - 1; + if (num < bounds[bound]) { + return bounds[bound + 1] - 1; + } + return num; + } + + int getNextShape(int cur) { + int bound = edgesN[cur] >> 16; + return bounds[bound + 1]; + } + + void init() { + + edgesYS = new int[edgesCount]; + System.arraycopy(edgesY, 0, edgesYS, 0, edgesCount); + // Create edgesDY + edgesDY = new int[edgesCount]; + for(int i = 0; i < edgesCount; i++) { + int dy = edgesY[getNext(i)] - edgesY[i]; + edgesDY[i] = dy; + } + + // Create edgesExt + edgesExt = new boolean[edgesCount]; + int prev = -1; + int i = 0; + int pos = 0; + while(i < edgesCount) { + + TOP: { + do { + if (edgesDY[i] > 0) { + break TOP; + } + i = getNext(i); + } while (i != pos); + i = pos = getNextShape(i); + continue; + } + + BOTTOM: { + do { + if (edgesDY[i] < 0) { + break BOTTOM; + } + if (edgesDY[i] > 0) { + prev = i; + } + i = getNext(i); + } while (i != pos); + i = pos = getNextShape(i); + continue; + } + + if (prev != -1) { + edgesExt[prev] = true; + } + edgesExt[i] = true; + } + + // Sort edgesY and edgesN + sort(edgesYS, edgesN, edgesCount); + + edgeCur = 0; + activeCount = 0; + activeX = new float[edgesCount]; + activeYEnd = new int[edgesCount]; + activeXStep = new float[edgesCount]; + activeDY = new int[edgesCount]; + activeExt = new boolean[edgesCount]; + + crossX = new int[edgesCount]; + crossDY = new int[edgesCount]; + } + + /** + * Marks edge as active + */ + void addActiveEdge(int levelY, int start, int end, boolean back) { + int dy = back ? -edgesDY[end] : edgesDY[start]; + if (dy <= 0) { + return; + } + int x1 = edgesX[start]; + int dx = edgesX[end] - x1; + activeX[activeCount] = x1; + activeYEnd[activeCount] = edgesY[end]; + activeXStep[activeCount] = dx / (float)dy; + activeDY[activeCount] = back ? -dy : dy; + activeExt[activeCount] = back ? edgesExt[end] : edgesExt[start]; + activeCount++; + } + + /** + * Find new active edges + */ + int findActiveEdges(int levelY) { + + int edgeActive = edgeCur; + while (edgeActive < edgesCount && edgesYS[edgeActive] == levelY) { + edgeActive++; + } + + int activeNext = edgeActive; + + while (edgeActive > edgeCur) { + edgeActive--; + int num = edgesN[edgeActive] & 0xFFFF; + addActiveEdge(levelY, num, getPrev(edgeActive), true); + addActiveEdge(levelY, num, getNext(edgeActive), false); + } + + edgeCur = activeNext; + + if (activeNext == edgesCount) { + return edgesY[edgesCount - 1]; + } + return edgesYS[activeNext]; + } + + /** + * Rasterizes shape with particular flatness + * @param shape - the souze Shape to be rasterized + * @param flatness - the rasterization flatness + * @return a MultiRectArea of rasterized shape + */ + public MultiRectArea rasterize(Shape shape, double flatness) { + + PathIterator path = shape.getPathIterator(null, flatness); + + // Shape is empty + if (path.isDone()) { + return new MultiRectArea(); + } + + makeBuffer(path, flatness); + + init(); + + int y = edgesYS[0]; + int nextY = y; + int crossCount; + + MultiRectArea.LineCash rect = new MultiRectArea.LineCash(edgesCount); + rect.setLine(y); + + while(y <= nextY) { + + crossCount = 0; + + if (y == nextY) { + + int i = activeCount; + while(i > 0) { + i--; + if (activeYEnd[i] == y) { + + activeCount--; + int length = activeCount - i; + if (length != 0) { + int pos = i + 1; + System.arraycopy(activeX, pos, activeX, i, length); + System.arraycopy(activeYEnd, pos, activeYEnd, i, length); + System.arraycopy(activeXStep, pos, activeXStep, i, length); + System.arraycopy(activeDY, pos, activeDY, i, length); + System.arraycopy(activeExt, pos, activeExt, i, length); + } + } + } + + nextY = findActiveEdges(y); + } + + // Get X crossings + for(int i = 0; i < activeCount; i++) { + crossX[crossCount] = (int)Math.ceil(activeX[i]); + crossDY[crossCount] = activeDY[i]; + crossCount++; + } + + if (crossCount == 0) { + rect.skipLine(); + } else { + // Sort X crossings + sort(crossX, crossDY, crossCount); + filler.add(rect, crossX, crossDY, crossCount, y); + } + + for(int i = 0; i < activeCount; i++) { + activeX[i] += activeXStep[i]; + } + + y++; + } + + return rect; + } + +} diff --git a/awt/org/apache/harmony/awt/gl/render/JavaTextRenderer.java b/awt/org/apache/harmony/awt/gl/render/JavaTextRenderer.java new file mode 100644 index 0000000..322ba57 --- /dev/null +++ b/awt/org/apache/harmony/awt/gl/render/JavaTextRenderer.java @@ -0,0 +1,263 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Ilya S. Okomin + * @version $Revision$ + */ +package org.apache.harmony.awt.gl.render; + +import java.awt.*; +import java.awt.image.*; + + +import java.awt.font.GlyphMetrics; +import java.awt.font.GlyphVector; +import java.awt.geom.AffineTransform; +import java.awt.geom.Point2D; + +import org.apache.harmony.awt.gl.TextRenderer; +import org.apache.harmony.awt.gl.font.CommonGlyphVector; +import org.apache.harmony.awt.gl.font.FontPeerImpl; +import org.apache.harmony.awt.gl.font.Glyph; +import org.apache.harmony.awt.gl.image.BufferedImageGraphics2D; + +public class JavaTextRenderer extends TextRenderer { + + public static final JavaTextRenderer inst = new JavaTextRenderer(); + + @Override + public void drawGlyphVector(Graphics2D g, GlyphVector glyphVector, + float x, float y) { + + AffineTransform at = g.getTransform(); + Rectangle c = g.getClipBounds(); + if (at != null){ + int atType = at.getType(); + if (atType == AffineTransform.TYPE_TRANSLATION) { + c.translate((int)Math.round(at.getTranslateX()), (int)Math.round(at.getTranslateY())); + } + } + + WritableRaster wr = ((BufferedImageGraphics2D)g).getWritableRaster(); + ColorModel cm = ((BufferedImageGraphics2D)g).getColorModel(); + + Rectangle rBounds = wr.getBounds(); + + Object color = cm.getDataElements(g.getColor().getRGB(), null); + + drawClipGlyphVector(wr, color, glyphVector, (int)Math.round(x + at.getTranslateX()), (int)Math.round(y + at.getTranslateY()), + Math.max(c.x,rBounds.x), + Math.max(c.y,rBounds.y), + Math.min((int)Math.round(c.getMaxX()), (int)Math.round(rBounds.getMaxX())), + Math.min((int)Math.round(c.getMaxY()), (int)Math.round(rBounds.getMaxY()))); + + } + + @SuppressWarnings("deprecation") + @Override + public void drawString(Graphics2D g, String str, float x, float y) { + AffineTransform at = g.getTransform(); + Rectangle c = g.getClipBounds(); + if (at != null){ + int atType = at.getType(); + if (atType == AffineTransform.TYPE_TRANSLATION) { + c.translate((int)Math.round(at.getTranslateX()), (int)Math.round(at.getTranslateY())); + } + } + WritableRaster wr = ((BufferedImageGraphics2D)g).getWritableRaster(); + ColorModel cm = ((BufferedImageGraphics2D)g).getColorModel(); + Rectangle rBounds = wr.getBounds(); + + Object color = cm.getDataElements(g.getColor().getRGB(), null); + + drawClipString(wr, color, str, (FontPeerImpl) (g.getFont().getPeer()), + (int)Math.round(x + at.getTranslateX()), (int)Math.round(y + at.getTranslateY()), + Math.max(c.x,rBounds.x), + Math.max(c.y,rBounds.y), + Math.min((int)Math.round(c.getMaxX()), (int)Math.round(rBounds.getMaxX())), + Math.min((int)Math.round(c.getMaxY()), (int)Math.round(rBounds.getMaxY()))); + + } + + /** + * + * Draws string on specified raster at desired position. + * + * @param raster specified WritableRaster to draw at + * @param color color of the text + * @param glyphVector GlyphVector object to draw + * @param x start X position to draw + * @param y start Y position to draw + * @param cMinX minimum x of the raster area to draw + * @param cMinY minimum y of the raster area to draw + * @param cMaxX maximum x of the raster area to draw + * @param cMaxY maximum y of the raster area to draw + */ + public void drawClipGlyphVector(WritableRaster raster, Object color, + GlyphVector glyphVector, int x, int y, + int cMinX, int cMinY, int cMaxX, int cMaxY) { + // TODO: implement complex clipping + + int xSrcSurf, ySrcSurf; // Start point in String rectangle + int xDstSurf, yDstSurf; // Start point in Surface rectangle + int clWidth, clHeight; + + for (int i = 0; i < glyphVector.getNumGlyphs(); i++) { + Glyph gl = ((CommonGlyphVector) glyphVector).vector[i]; + + if (gl.getPointWidth() == 0) { + continue; + } + + byte[] data = gl.getBitmap(); + if (data != null) { + Point2D pos = glyphVector.getGlyphPosition(i); + + xSrcSurf = 0;//gl.bmp_left; + ySrcSurf = 0;//gl.bmp_rows - gl.bmp_top; + + xDstSurf = x + (int)pos.getX() + (int) gl.getGlyphPointMetrics().getLSB();// + gl.bmp_left; + yDstSurf = y - gl.bmp_top/*getPointHeight()*/ + (int) pos.getY();// - (gl.bmp_rows-gl.bmp_top); + + int textWidth = gl.bmp_width; + int textHeight = gl.getPointHeight(); + + // if Regions don't intersect + if ((xDstSurf > cMaxX) || (yDstSurf > cMaxY) || (xDstSurf + textWidth < cMinX) + || (yDstSurf + textHeight < cMinY)) { + // Nothing to do + } else { + if (xDstSurf >= cMinX) { + clWidth = Math.min(textWidth, cMaxX - xDstSurf); + } else { + xSrcSurf += cMinX - xDstSurf; + clWidth = Math.min(cMaxX - cMinX, textWidth - (cMinX - xDstSurf)); + xDstSurf = cMinX; + } + if (yDstSurf >= cMinY) { + clHeight = Math.min(textHeight, cMaxY - yDstSurf); + } else { + ySrcSurf += cMinY - yDstSurf; + clHeight = Math.min(cMaxY - cMinY, textHeight - (cMinY - yDstSurf)); + yDstSurf = cMinY; + } + // Drawing on the Raster + for (int h=0; h cMaxX) || (yDstSurf > cMaxY) || (xDstSurf + textWidth < cMinX) + || (yDstSurf + textHeight < cMinY)) { + // Nothing to do + } else { + if (xDstSurf >= cMinX) { + clWidth = Math.min(textWidth, cMaxX - xDstSurf); + } else { + xSrcSurf += cMinX - xDstSurf; + clWidth = Math.min(cMaxX - cMinX, textWidth - (cMinX - xDstSurf)); + xDstSurf = cMinX; + } + if (yDstSurf >= cMinY) { + clHeight = Math.min(textHeight, cMaxY - yDstSurf); + } else { + ySrcSurf += cMinY - yDstSurf; + clHeight = Math.min(cMaxY - cMinY, textHeight - (cMinY - yDstSurf)); + yDstSurf = cMinY; + } + + // Drawing on the Raster + for (int h=0; h imInstances; // Map + private final Map localeIM; // Map last user-selected IM for locale + private final Set notifyIM; // set of IMs to notify of client window bounds changes + + /** + * a flag indicating that IM should be notified of client window + * position/visibility changes as soon as it is activated(new client + * appears) + */ + private boolean pendingClientNotify; + private Component nextComp; // component to gain focus after endComposition() + //???AWT: private final Set imWindows; // set of all IM windows created by this instance + private final NativeIM nativeIM; + + + + public InputMethodContext() { + notifyIM = new HashSet(); +//???AWT: imWindows = new HashSet(); + imInstances = new HashMap(); + localeIM = new HashMap(); + selectInputMethod(Locale.US); // not default? + nativeIM = (NativeIM) inputMethod; + } + + //???AWT + /* + @Override + public void dispatchEvent(AWTEvent event) { + int id = event.getID(); + if ((id >= FocusEvent.FOCUS_FIRST) && (id <=FocusEvent.FOCUS_LAST)) { + dispatchFocusEvent((FocusEvent) event); + } else { + // handle special KEY_PRESSED + // event to show IM selection menu + if (id == KeyEvent.KEY_PRESSED) { + KeyEvent ke = (KeyEvent) event; + IMManager.selectIM(ke, this, + IMManager.getWindow(ke.getComponent())); + } + // dispatch all input events to the current IM: + if (inputMethod != null) { + inputMethod.dispatchEvent(event); + } + } + } + + private void dispatchFocusEvent(FocusEvent fe) { + switch (fe.getID()) { + case FocusEvent.FOCUS_LOST: + if (inputMethod != null) { + inputMethod.deactivate(fe.isTemporary()); + } + break; + case FocusEvent.FOCUS_GAINED: + + Component comp = fe.getComponent(); + if (imWindows.contains(comp)) { + // prevent activating when IM windows + // attached to this context gain focus + return; + } + InputMethodContext lastActive = IMManager.getLastActiveIMC(); + if ((lastActive != this) && (lastActive != null)) { + lastActive.hideWindows(); + } + if (inputMethod != null) { + activateIM(inputMethod); + if (!getCompositionWindow().isEmpty()) { + IMManager.showCompositionWindow(composeWindow); + } + if (client == comp) { + if (nextComp != null) { + // temporarily got focus to + // end composition + endComposition(); + + // transfer focus to new client + client = nextComp; + nextComp = null; + client.requestFocusInWindow(); + } + } else if ((client != null) && getCompositionWindow().isVisible()) { + // temporarily return focus back + // to previous client to be able + // to end composition + nextComp = comp; + client.requestFocusInWindow(); + } else { + client = comp; + } + } + if (pendingClientNotify) { + notifyClientWindowChange(IMManager.getWindow(comp).getBounds()); + } + break; + } + + } + + private void activateIM(InputMethod im) { + im.activate(); + if ((nativeIM != null) && (im != nativeIM)) { + // when Java IM is active + // native input method editor must be + // explicitly disabled + nativeIM.disableIME(); + } + IMManager.setLastActiveIMC(this); + } + + @SuppressWarnings("deprecation") + private void hideWindows() { + if (inputMethod != null) { + inputMethod.hideWindows(); + } + if (composeWindow != null) { + composeWindow.hide(); + } + } + + private void createCompositionWindow() { + composeWindow = new CompositionWindow(client); + } + + private CompositionWindow getCompositionWindow() { + if (composeWindow == null) { + createCompositionWindow(); + } + composeWindow.setClient(client); + return composeWindow; + } + */ + + /** + * Gets input method requests for the current client + * irrespective of input style. + * @return input method requests of composition window if + * client is passive, + * otherwise input method requests of client + */ + private InputMethodRequests getIMRequests() { + InputMethodRequests imRequests = null; + + if (client != null) { + imRequests = client.getInputMethodRequests(); + //???AWT + /* + if (imRequests == null) { + imRequests = getCompositionWindow().getInputMethodRequests(); + } + */ + } + + return imRequests; + } + + /** + * Gets input method requests for the current client & input style. + * @return input method requests of composition window if + * input style is "below-the-spot"(or client is passive), + * otherwise client input method requests + */ + private InputMethodRequests getStyleIMRequests() { + //???AWT + /* + if (IMManager.belowTheSpot()) { + return getCompositionWindow().getInputMethodRequests(); + } + */ + return getIMRequests(); + } + + @Override + public void dispose() { + if (inputMethod != null) { + closeIM(inputMethod); + inputMethod.dispose(); + } + notifyIM.clear(); + super.dispose(); + } + + @Override + public void endComposition() { + if (inputMethod != null) { + inputMethod.endComposition(); + } + super.endComposition(); + } + + @Override + public Object getInputMethodControlObject() { + if (inputMethod != null) { + return inputMethod.getControlObject(); + } + return super.getInputMethodControlObject(); + } + + @Override + public Locale getLocale() { + if (inputMethod != null) { + return inputMethod.getLocale(); + } + return super.getLocale(); + } + + @Override + public boolean isCompositionEnabled() { + if (inputMethod != null) { + return inputMethod.isCompositionEnabled(); + } + return super.isCompositionEnabled(); + } + + @Override + public void reconvert() { + if (inputMethod != null) { + inputMethod.reconvert(); + } + super.reconvert(); + } + + //???AWT + /* + @Override + public void removeNotify(Component client) { + if ((inputMethod != null) && (client == this.client)) { + inputMethod.removeNotify(); + client = null; + // set flag indicating that IM should be notified + // as soon as it is activated(new client appears) + pendingClientNotify = true; + } + + super.removeNotify(client); + } + */ + + @Override + public boolean selectInputMethod(Locale locale) { + + if ((inputMethod != null) && inputMethod.setLocale(locale)) { + return true; + } + // first + // take last user-selected IM for locale + InputMethod newIM = localeIM.get(locale); + + // if not found search through IM descriptors + // and take already created instance if exists + // or create, store new IM instance in descriptor->instance map + //???AWT + /* + if (newIM == null) { + try { + newIM = getIMInstance(IMManager.getIMDescriptors().iterator(), + locale); + } catch (Exception e) { + // ignore exceptions - just return false + } + } + */ + + return switchToIM(locale, newIM); + } + + private boolean switchToIM(Locale locale, InputMethod newIM) { + //???AWT + /* + if (newIM != null) { + closeIM(inputMethod); + client = KeyboardFocusManager. + getCurrentKeyboardFocusManager().getFocusOwner(); + initIM(newIM, locale); + inputMethod = newIM; + + return true; + } + */ + return false; + } + + /** + * Is called when IM is selected from UI + */ + void selectIM(InputMethodDescriptor imd, Locale locale) { + try { + switchToIM(locale, getIMInstance(imd)); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * Gets input method instance for the given + * locale from the given list of descriptors + * @param descriptors iterator of the list of IM descriptors + * @param locale the locale to be supported by the IM + * @return input method instance + * @throws Exception + */ + private InputMethod getIMInstance(Iterator descriptors, + Locale locale) throws Exception { + while (descriptors.hasNext()) { + InputMethodDescriptor desc = descriptors.next(); + Locale[] locs = desc.getAvailableLocales(); + for (Locale element : locs) { + if (locale.equals(element)) { + return getIMInstance(desc); + } + } + } + return null; + } + + private InputMethod getIMInstance(InputMethodDescriptor imd) throws Exception { + InputMethod im = imInstances.get(imd); + if (im == null) { + im = imd.createInputMethod(); + im.setInputMethodContext(this); + imInstances.put(imd, im); + } + return im; + } + + private void initIM(InputMethod im, Locale locale) { + if (im == null) { + return; + } + im.setLocale(locale); + im.setCharacterSubsets(null); + //???AWT: activateIM(im); + try { + im.setCompositionEnabled(inputMethod != null ? + inputMethod.isCompositionEnabled() : true); + } catch (UnsupportedOperationException uoe) { + + } + + } + + private void closeIM(InputMethod im) { + if (im == null) { + return; + } + if (im.isCompositionEnabled()) { + im.endComposition(); + } + + im.deactivate(true); + im.hideWindows(); + + } + + @Override + public void setCharacterSubsets(Subset[] subsets) { + if (inputMethod != null) { + inputMethod.setCharacterSubsets(subsets); + } + super.setCharacterSubsets(subsets); + } + + @Override + public void setCompositionEnabled(boolean enable) { + if (inputMethod != null) { + inputMethod.setCompositionEnabled(enable); + } + super.setCompositionEnabled(enable); + } + + //???AWT + /* + public JFrame createInputMethodJFrame(String title, + boolean attachToInputContext) { + JFrame jf = new IMJFrame(title, attachToInputContext ? this : null); + imWindows.add(jf); + return jf; + } + + public Window createInputMethodWindow(String title, + boolean attachToInputContext) { + Window w = new IMWindow(title, attachToInputContext ? this : null); + imWindows.add(w); + return w; + } + */ + + @SuppressWarnings("deprecation") + public void dispatchInputMethodEvent(int id, + AttributedCharacterIterator text, + int committedCharacterCount, + TextHitInfo caret, + TextHitInfo visiblePosition) { + if (client == null) { + return; + } + //???AWT + /* + InputMethodEvent ime = new InputMethodEvent(client, id, text, + committedCharacterCount, + caret, visiblePosition); + + + if ((client.getInputMethodRequests() != null) && + !IMManager.belowTheSpot()) { + + client.dispatchEvent(ime); + } else { + + // show/hide composition window if necessary + if (committedCharacterCount < text.getEndIndex()) { + IMManager.showCompositionWindow(getCompositionWindow()); + } else { + getCompositionWindow().hide(); + } + composeWindow.getActiveClient().dispatchEvent(ime); + } + */ + + } + + public void enableClientWindowNotification(InputMethod inputMethod, + boolean enable) { + if (enable) { + notifyIM.add(inputMethod); + //???AWT + /* + if (client != null) { + notifyClientWindowChange(IMManager.getWindow(client).getBounds()); + } else { + pendingClientNotify = true; + } + */ + } else { + notifyIM.remove(inputMethod); + } + + } + + public AttributedCharacterIterator cancelLatestCommittedText( + Attribute[] attributes) { + return getIMRequests().cancelLatestCommittedText(attributes); + } + + public AttributedCharacterIterator getCommittedText(int beginIndex, + int endIndex, + Attribute[] attributes) { + return getIMRequests().getCommittedText(beginIndex, endIndex, + attributes); + } + + public int getCommittedTextLength() { + return getIMRequests().getCommittedTextLength(); + } + + public int getInsertPositionOffset() { + return getIMRequests().getInsertPositionOffset(); + } + + public TextHitInfo getLocationOffset(int x, int y) { + InputMethodRequests imr = getStyleIMRequests(); + if (imr != null) { + return imr.getLocationOffset(x, y); + } + return null; + } + + public AttributedCharacterIterator getSelectedText(Attribute[] attributes) { + return getIMRequests().getSelectedText(attributes); + } + + public Rectangle getTextLocation(TextHitInfo offset) { + return getStyleIMRequests().getTextLocation(offset); + } + + /** + * To be called by AWT when client Window's bounds/visibility/state + * change + */ + public void notifyClientWindowChange(Rectangle bounds) { + if (notifyIM.contains(inputMethod)) { + inputMethod.notifyClientWindowChange(bounds); + } + pendingClientNotify = false; + } + + public final InputMethod getInputMethod() { + return inputMethod; + } + + public final Component getClient() { + return client; + } + + public final NativeIM getNativeIM() { + return nativeIM; + } +} diff --git a/awt/org/apache/harmony/awt/internal/nls/Messages.java b/awt/org/apache/harmony/awt/internal/nls/Messages.java new file mode 100644 index 0000000..96762c9 --- /dev/null +++ b/awt/org/apache/harmony/awt/internal/nls/Messages.java @@ -0,0 +1,143 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * THE FILE HAS BEEN AUTOGENERATED BY MSGTOOL TOOL. + * All changes made to this file manually will be overwritten + * if this tool runs again. Better make changes in the template file. + */ + +package org.apache.harmony.awt.internal.nls; + + +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.Locale; +import java.util.MissingResourceException; +import java.util.ResourceBundle; + +// BEGIN android-deleted +/* + * For Android, this module is a separate library and not part of the + * boot classpath, so its resources won't be found on the boot classpath + * as is assumed by MsgHelp.getString(). We instead use a local MsgHelp + * which bottoms out in a call to the useful part of its lower-level + * namesake. + */ +//import org.apache.harmony.kernel.vm.VM; +//import org.apache.harmony.luni.util.MsgHelp; +// END android-deleted + +/** + * This class retrieves strings from a resource bundle and returns them, + * formatting them with MessageFormat when required. + *

+ * It is used by the system classes to provide national language support, by + * looking up messages in the + * org.apache.harmony.awt.internal.nls.messages + * + * resource bundle. Note that if this file is not available, or an invalid key + * is looked up, or resource bundle support is not available, the key itself + * will be returned as the associated message. This means that the KEY + * should a reasonable human-readable (english) string. + * + */ +public class Messages { + + // BEGIN android-deleted + //private static final String sResource = + // "org.apache.harmony.awt.internal.nls.messages"; + // END android-deleted + + /** + * Retrieves a message which has no arguments. + * + * @param msg + * String the key to look up. + * @return String the message for that key in the system message bundle. + */ + static public String getString(String msg) { + return MsgHelp.getString(msg); + } + + /** + * Retrieves a message which takes 1 argument. + * + * @param msg + * String the key to look up. + * @param arg + * Object the object to insert in the formatted output. + * @return String the message for that key in the system message bundle. + */ + static public String getString(String msg, Object arg) { + return getString(msg, new Object[] { arg }); + } + + /** + * Retrieves a message which takes 1 integer argument. + * + * @param msg + * String the key to look up. + * @param arg + * int the integer to insert in the formatted output. + * @return String the message for that key in the system message bundle. + */ + static public String getString(String msg, int arg) { + return getString(msg, new Object[] { Integer.toString(arg) }); + } + + /** + * Retrieves a message which takes 1 character argument. + * + * @param msg + * String the key to look up. + * @param arg + * char the character to insert in the formatted output. + * @return String the message for that key in the system message bundle. + */ + static public String getString(String msg, char arg) { + return getString(msg, new Object[] { String.valueOf(arg) }); + } + + /** + * Retrieves a message which takes 2 arguments. + * + * @param msg + * String the key to look up. + * @param arg1 + * Object an object to insert in the formatted output. + * @param arg2 + * Object another object to insert in the formatted output. + * @return String the message for that key in the system message bundle. + */ + static public String getString(String msg, Object arg1, Object arg2) { + return getString(msg, new Object[] { arg1, arg2 }); + } + + /** + * Retrieves a message which takes several arguments. + * + * @param msg + * String the key to look up. + * @param args + * Object[] the objects to insert in the formatted output. + * @return String the message for that key in the system message bundle. + */ + static public String getString(String msg, Object[] args) { + return MsgHelp.getString(msg, args); + } +} diff --git a/awt/org/apache/harmony/awt/internal/nls/MsgHelp.java b/awt/org/apache/harmony/awt/internal/nls/MsgHelp.java new file mode 100644 index 0000000..b57fe11 --- /dev/null +++ b/awt/org/apache/harmony/awt/internal/nls/MsgHelp.java @@ -0,0 +1,86 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * This implementation is based on the class of the same name in + * org.apache.harmony.luni.util. + */ + +package org.apache.harmony.awt.internal.nls; + +import java.io.IOException; +import java.io.InputStream; +import java.util.logging.Logger; +import java.util.Locale; +import java.util.PropertyResourceBundle; +import java.util.ResourceBundle; +import java.util.MissingResourceException; + +/** + * This class contains helper methods for loading resource bundles and + * formatting external message strings. + */ +public final class MsgHelp { + /** name of the resource for this class */ + private static final String RESOURCE_NAME = + "/org/apache/harmony/awt/internal/nls/messages.properties"; + + /** the resource bundle for this class */ + private static final ResourceBundle THE_BUNDLE; + + static { + ResourceBundle rb = null; + + try { + InputStream in = MsgHelp.class.getResourceAsStream( + RESOURCE_NAME); + rb = new PropertyResourceBundle(in); + } catch (IOException ex) { + Logger.global.warning("Couldn't read resource bundle: " + + ex); + } catch (RuntimeException ex) { + // Shouldn't happen, but deal at least somewhat gracefully. + Logger.global.warning("Couldn't find resource bundle: " + + ex); + } + + THE_BUNDLE = rb; + } + + public static String getString(String msg) { + if (THE_BUNDLE == null) { + return msg; + } + try { + return THE_BUNDLE.getString(msg); + } catch (MissingResourceException e) { + return "Missing message: " + msg; + } + } + + static public String getString(String msg, Object[] args) { + String format = msg; + if (THE_BUNDLE != null) { + try { + format = THE_BUNDLE.getString(msg); + } catch (MissingResourceException e) { + } + } + + return org.apache.harmony.luni.util.MsgHelp.format(format, args); + } +} diff --git a/awt/org/apache/harmony/awt/state/MenuItemState.java b/awt/org/apache/harmony/awt/state/MenuItemState.java new file mode 100644 index 0000000..b13e50b --- /dev/null +++ b/awt/org/apache/harmony/awt/state/MenuItemState.java @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Pavel Dolgov + * @version $Revision$ + */ +package org.apache.harmony.awt.state; + +import java.awt.Dimension; +import java.awt.Rectangle; + +/** + * State of menu item + */ + +public interface MenuItemState { + + String getText(); + Rectangle getTextBounds(); + void setTextBounds(int x, int y, int w, int h); + + String getShortcut(); + Rectangle getShortcutBounds(); + void setShortcutBounds(int x, int y, int w, int h); + + Rectangle getItemBounds(); + void setItemBounds(int x, int y, int w, int h); + + boolean isMenu(); + boolean isChecked(); + boolean isEnabled(); + + boolean isCheckBox(); + boolean isSeparator(); + + Dimension getMenuSize(); +} diff --git a/awt/org/apache/harmony/awt/state/MenuState.java b/awt/org/apache/harmony/awt/state/MenuState.java new file mode 100644 index 0000000..564a49a --- /dev/null +++ b/awt/org/apache/harmony/awt/state/MenuState.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Pavel Dolgov + * @version $Revision$ + */ +package org.apache.harmony.awt.state; + +import java.awt.Font; +import java.awt.FontMetrics; +import java.awt.Point; + +/** + * State of pop-up or drop-down menu + */ + +public interface MenuState { + int getWidth(); + int getHeight(); + Point getLocation(); + + void setSize(int w, int h); + + Font getFont(); + boolean isFontSet(); + FontMetrics getFontMetrics(Font f); + + int getItemCount(); + int getSelectedItemIndex(); + + MenuItemState getItem(int index); +} diff --git a/awt/org/apache/harmony/awt/state/State.java b/awt/org/apache/harmony/awt/state/State.java new file mode 100644 index 0000000..4b8706d --- /dev/null +++ b/awt/org/apache/harmony/awt/state/State.java @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Pavel Dolgov + * @version $Revision$ + */ +package org.apache.harmony.awt.state; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.FontMetrics; +import java.awt.Rectangle; + +/** + * State of the component + */ +public interface State { + + boolean isEnabled(); + boolean isVisible(); + boolean isFocused(); + + Font getFont(); + boolean isFontSet(); + FontMetrics getFontMetrics(); + + Color getBackground(); + boolean isBackgroundSet(); + + Color getTextColor(); + boolean isTextColorSet(); + + Rectangle getBounds(); + Dimension getSize(); + + Dimension getDefaultMinimumSize(); + void setDefaultMinimumSize(Dimension size); + + long getWindowId(); +} diff --git a/awt/org/apache/harmony/awt/wtk/CreationParams.java b/awt/org/apache/harmony/awt/wtk/CreationParams.java new file mode 100644 index 0000000..63c581d --- /dev/null +++ b/awt/org/apache/harmony/awt/wtk/CreationParams.java @@ -0,0 +1,133 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Dmitry A. Durnev + * @version $Revision$ + */ +package org.apache.harmony.awt.wtk; + +/** + * This class describes cross-platform NativeWindow creation params + * See also WindowFactory.createWindow + */ +public class CreationParams { + /** + * Initial state is maximized verticaly + */ + public final long MAXIMIZED_VERT = 1; + /** + * Initial state is maximized horizontaly + */ + public final long MAXIMIZED_HORIZ = 2; + /** + * Initial state is maximized both + * horizontaly and verticaly + */ + public final long MAXIMIZED = 3; + + /** + * The top-level window that has all possible decorations, + * has no owner and is displayed in taskbar + */ + public final static int DECOR_TYPE_FRAME = 1; + /** + * The dialog window + */ + public final static int DECOR_TYPE_DIALOG = 2; + /** + * The transient undecorated pop-up window + */ + public final static int DECOR_TYPE_POPUP = 3; + /** + * The undecoraded pop-up window + */ + public final static int DECOR_TYPE_UNDECOR = 4; + /** + * Non-MDI child window + */ + public final static int DECOR_TYPE_NONE = 0; + + /** + * Initial x. + */ + public int x = 0; + /** + * Initial y. + */ + public int y = 0; + /** + * Initial width. + */ + public int w = 1; + /** + * Initial height. + */ + public int h = 1; + /** + * The decoration type of the top-level window. The possible values are: + * DECOR_TYPE_FRAME, DECOR_TYPE_DIALOG, DECOR_TYPE_POPUP and DECOR_TYPE_UNDECOR + */ + public int decorType = DECOR_TYPE_NONE; + /** + * Window is child of parent, otherwise it's + * toplevel(child of desktop) window owned by parent. + */ + public boolean child = false; + /** + * Window is resizable + */ + public boolean resizable = true; + /** + * The window has no decorations + */ + public boolean undecorated = false; + /** + * Initial visibility state. + */ + public boolean visible = false; + /** + * Window is ALWAYS topmost in Z order. + */ + public boolean topmost = false; + /** + * Window is disabled. + */ + public boolean disabled = false; + /** + * Window initially iconified. + */ + public boolean iconified = false; + /** + * Bitwise OR of MAXIMIZED_* constants. + * Means if window is initially maximized. + */ + public int maximizedState = 0; + /** + * Tells that window position should be determined by native windowing system + */ + public boolean locationByPlatform = false; + /** + * Id of parent or owner window, see child field + * For non-child window without owner equals 0. + */ + public long parentId = 0; + /** + * Name wich is displayed on titlebar, taskbar and visible + * for system requests. + */ + public String name = null; +} \ No newline at end of file diff --git a/awt/org/apache/harmony/awt/wtk/CursorFactory.java b/awt/org/apache/harmony/awt/wtk/CursorFactory.java new file mode 100644 index 0000000..35e7d33 --- /dev/null +++ b/awt/org/apache/harmony/awt/wtk/CursorFactory.java @@ -0,0 +1,85 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Dmitry A. Durnev + * @version $Revision$ + */ +package org.apache.harmony.awt.wtk; + +import java.awt.Dimension; +import java.awt.Image; + +/** + * Provides factory for NativeCursor + */ +public abstract class CursorFactory { + protected NativeCursor[] systemCursors = { + null, null, null, null, + null, null, null, null, + null, null, null, null, + null, null, + }; + /** + * Creates and returns NativeCursor for predefined + * Java Cursor + * + * @param type - type of predefined Java Cursor + * @return created cursor + */ + public abstract NativeCursor createCursor(int type); + + /** + * Gets a cached instance of system(predefined) native cursor + * or creates a new one. This is a platform-independent method. + * + * @param type - type of predefined Java Cursor + * @return created cursor + */ + public NativeCursor getCursor(int type) { + if (type >= 0 && type < systemCursors.length) { + NativeCursor cursor = systemCursors[type]; + if (cursor == null) { + cursor = createCursor(type); + systemCursors[type] = cursor; + } + return cursor; + } + return null; + } + /** + * Creates and returns custom NativeCursor from image + * + * @param img - image(source) to create cursor from + * @param xHotSpot - x coordinate of the hotspot relative to the source's origin + * @param yHotSpot - y coordinate of the hotspot relative to the source's origin + * @return created cursor + */ + public abstract NativeCursor createCustomCursor(Image img, int xHotSpot, int yHotSpot); + + /** + * Query native system for the best cursor size closest to specified dimensions + * @param prefWidth - preferred width + * @param prefHeight - preferred height + * @return closest supported dimensions to ones specified + */ + public abstract Dimension getBestCursorSize(int prefWidth, int prefHeight); + + /** + * @return maximum number of colors supported by custom cursors + */ + public abstract int getMaximumCursorColors(); +} diff --git a/awt/org/apache/harmony/awt/wtk/GraphicsFactory.java b/awt/org/apache/harmony/awt/wtk/GraphicsFactory.java new file mode 100644 index 0000000..0d7c84f --- /dev/null +++ b/awt/org/apache/harmony/awt/wtk/GraphicsFactory.java @@ -0,0 +1,82 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Pavel Dolgov, Alexey A. Petrenko, Oleg V. Khaschansky + * @version $Revision$ + */ +package org.apache.harmony.awt.wtk; + +import java.awt.Font; +import java.awt.FontMetrics; +import java.awt.Graphics2D; +import java.awt.GraphicsEnvironment; +import java.awt.peer.FontPeer; +import org.apache.harmony.awt.gl.MultiRectArea; +import org.apache.harmony.awt.gl.font.FontManager; + +import android.graphics.Canvas; +import android.graphics.Paint; + + +/** + * GraphicsFactory interface defines methods for Graphics2D + * and font stuff instances factories. + */ +public interface GraphicsFactory { + static final FontMetrics cacheFM[] = new FontMetrics[10]; + + /** + * This method creates Graphics2D instance for specified native window. + * + * @param win Native window to draw + * @param translateX Translation along X axis + * @param translateY Translation along Y axis + * @param clip Clipping area for a new Graphics2D instance + * @return New Graphics2D instance for specified native window + * @deprecated + */ + @Deprecated + Graphics2D getGraphics2D(NativeWindow win, int translateX, int translateY, MultiRectArea clip); + + /** + * This method creates Graphics2D instance for specified native window. + * + * @param win Native window to draw + * @param translateX Translation along X axis + * @param translateY Translation along Y axis + * @param width Width of drawing area + * @param height Height of drawing area + * @return New Graphics2D instance for specified native window + */ + Graphics2D getGraphics2D(NativeWindow win, int translateX, int translateY, int width, int height); + // ???AWT: not standard harmony + Graphics2D getGraphics2D(Canvas c, Paint p); + + /** + * Creates instance of GraphicsEnvironment for specified WindowFactory + * + * @param wf WindowFactory + * @return New instance of GraphicsEnvironment + */ + GraphicsEnvironment createGraphicsEnvironment(WindowFactory wf); + + // Font methods + FontMetrics getFontMetrics(Font font); + FontManager getFontManager(); + FontPeer getFontPeer(Font font); + Font embedFont(String fontFilePath); +} diff --git a/awt/org/apache/harmony/awt/wtk/KeyInfo.java b/awt/org/apache/harmony/awt/wtk/KeyInfo.java new file mode 100644 index 0000000..1f8a29a --- /dev/null +++ b/awt/org/apache/harmony/awt/wtk/KeyInfo.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Pavel Dolgov + * @version $Revision$ + */ +package org.apache.harmony.awt.wtk; + +import java.awt.event.KeyEvent; + +/** + * Keystroke information + */ + +public final class KeyInfo { + + public int vKey; + public int keyLocation; + public final StringBuffer keyChars; + + public static final int DEFAULT_VKEY = KeyEvent.VK_UNDEFINED; + public static final int DEFAULT_LOCATION = KeyEvent.KEY_LOCATION_STANDARD; + + public KeyInfo() { + vKey = DEFAULT_VKEY; + keyLocation = DEFAULT_LOCATION; + keyChars = new StringBuffer(); + } + + public void setKeyChars(char ch) { + keyChars.setLength(0); + keyChars.append(ch); + } + + public void setKeyChars(StringBuffer sb) { + keyChars.setLength(0); + keyChars.append(sb); + } +} diff --git a/awt/org/apache/harmony/awt/wtk/NativeCursor.java b/awt/org/apache/harmony/awt/wtk/NativeCursor.java new file mode 100644 index 0000000..2c6eb1e --- /dev/null +++ b/awt/org/apache/harmony/awt/wtk/NativeCursor.java @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Dmitry A. Durnev + * @version $Revision$ + */ +package org.apache.harmony.awt.wtk; + +/** + * The interface provides access to platform dependent functionality + * for the class java.awt.Cursor. + */ +public interface NativeCursor { + /** + * Sets the current cursor shape + * to this cursor when a pointer is inside + * @param winID - window(currently used only on X11) + */ + void setCursor(long winID); + /** + * Destroys the native resource associated with + * this cursor + */ + void destroyCursor(); + + /** + * @return Native handle associated with this cursor + */ + long getId(); + +} diff --git a/awt/org/apache/harmony/awt/wtk/NativeEvent.java b/awt/org/apache/harmony/awt/wtk/NativeEvent.java new file mode 100644 index 0000000..1471c1a --- /dev/null +++ b/awt/org/apache/harmony/awt/wtk/NativeEvent.java @@ -0,0 +1,268 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Mikhail Danilov + * @version $Revision$ + */ +package org.apache.harmony.awt.wtk; + +import java.awt.Insets; +import java.awt.Rectangle; +import java.awt.Point; +import java.awt.event.KeyEvent; + +import org.apache.harmony.awt.gl.MultiRectArea; + + +/** + * The interface describing cross-platform translation of system + * messages. + * + *

Some messages can appear only on specific platform, + * but they still can have cross-platform interpretation if the + * application should be aware of them and can react using + * cross-platform API. + * + */ +public abstract class NativeEvent { + + /** + * Message has no common cross-platform + * interpretation and should be skipped. + */ + public static final int ID_PLATFORM = 0; + + /** + * Window bounds have changed. + */ + public static final int ID_BOUNDS_CHANGED = -1; + + /** + * Window decoration size has changed. + */ + public static final int ID_INSETS_CHANGED = -2; + + /** + * Window was just created (WM_CREATE on Windows) + */ + public static final int ID_CREATED = -3; + + /** + * Mouse grab was canceled by the native system + */ + public static final int ID_MOUSE_GRAB_CANCELED = -4; + + /** + * System color scheme or visual theme was changed + */ + public static final int ID_THEME_CHANGED = -5; + + protected long windowId; + protected int eventId; + protected long otherWindowId; + + protected Point screenPos; + protected Point localPos; + protected Rectangle windowRect; + + protected int modifiers; + protected int mouseButton; + protected int wheelRotation; + + protected KeyInfo keyInfo = new KeyInfo(); + + protected int windowState = -1; + protected long time; + + /** + * Returns the system window id of the event recipient. + * @return HWND on Windows, xwindnow on X + */ + public long getWindowId() { + return windowId; + } + + /** + * Returns cross-platform event id + * should be one of ID_* constants or + * id constants from java.awt.AWTEvent subclasess + * @return cross-platform event id + */ + public int getEventId() { + return eventId; + } + + /** + * Returns the position of cursor when event occured relative to + * top-left corner of recipient window + * @return position of cursor in local coordinates + */ + public Point getLocalPos() { + return localPos; + } + + /** + * Returns the position of cursor when event occured + * in screen coordinates. + * @return position of cursor in screen coordinates + */ + public Point getScreenPos() { + return screenPos; + } + + /** + * The recipient window bounds when the event occured + * @return window bounds + */ + public Rectangle getWindowRect() { + return windowRect; + } + + /** + * Returns the state of keyboard and mouse buttons when the event + * occured if event from mouse or keyboard, for other events can + * return junk values. The value is bitwise OR of + * java.awt.event.InputEvent *_DOWN constants. + * + * Method is aware of system mouse button swap for left-hand + * mouse and return swapped values. + * @return bitwise OR of java.awt.event.InputEvent *_DOWN constants + */ + public int getInputModifiers() { + return modifiers; + } + + /** + * Returns the iconified/maximized state of recipient window if + * event is state related, for other events can junk values. + * The value has the same meaning as Frame.getExtendedState + * It's bitwise OR of ICONIFIED, MAXIMIZED_HORIZ, MAXIMIZED_VERT + * @return bitwise OR of ICONIFIED, MAXIMIZED_HORIZ, MAXIMIZED_VERT + */ + public int getWindowState() { + return windowState; + } + + /** + * The same meaning as java.awt.event.getKeyCode + * @return java.awt.event VK_* constant + */ + public int getVKey() { + return (keyInfo != null) ? keyInfo.vKey : KeyInfo.DEFAULT_VKEY; + } + + /** + * The same meaning as java.awt.event.getKeyLocation + * @return java.awt.event KEY_LOCATION_* constant + */ + public int getKeyLocation() { + return (keyInfo != null) ? keyInfo.keyLocation : KeyInfo.DEFAULT_LOCATION; + } + + /** + * Return the string of characters associated with the event + * Has meaning only for KEY_PRESSED as should be translated to + * serie of KEY_TYPED events. For dead keys and input methods + * one key press can generate multiple key chars. + * @return string of characters + */ + public StringBuffer getKeyChars() { + if (keyInfo == null) { + return null; + } + if (keyInfo.vKey == KeyEvent.VK_ENTER) { + keyInfo.keyChars.setLength(0); + keyInfo.setKeyChars('\n'); + } + return keyInfo.keyChars; + } + + public char getLastChar() { + if (keyInfo == null || keyInfo.keyChars.length() == 0) { + return KeyEvent.CHAR_UNDEFINED; + } + return keyInfo.keyChars.charAt(keyInfo.keyChars.length()-1); + } + + /** + * Returns the number of mouse button which changed it's state, + * otherwise 0. + * Left button is 1, middle button is 2, right button is 3. + * + * Method is aware of system mouse button swap for left-hand + * mouse and return swapped values. + * @return mouse button number + */ + public int getMouseButton() { + return mouseButton; + } + + /** + * Returns time when the message was received + * @return time in milliseconds + */ + public long getTime() { + return time; + } + + /** + * For the focus event contains the oposite window. + * This means it lost focus if recipient gains it, + * or will gain focus if recipient looses it. + * @return HWND on Windows, xwindnow on X + */ + public long getOtherWindowId() { + return otherWindowId; + } + + /** + * Returns the "dirty" area of the window as set of non-intersecting + * rectangles. This area is to be painted. + * @return non-empty array of null if empty + */ + public abstract MultiRectArea getClipRects(); + + /** + * Returns the "dirty" area of the window as one rectangle. + * This area is to be painted. + * @return non-null Rectangle + */ + public abstract Rectangle getClipBounds(); + + /** + * Returns the window insets. Insets is area which belongs to + * window somehow but is outside of it's client area, + * it usually contains system provided border and titlebar. + * @return non-null java.awt.Insets + */ + public abstract Insets getInsets(); + + /** + * Returns true if event is popup menu trigger. + * @return boolean flag + */ + public abstract boolean getTrigger(); + + /** + * Returns the number of "clicks" the mouse wheel was rotated. + * @return negative values if the mouse wheel was rotated up/away from the user, + * and positive values if the mouse wheel was rotated down/ towards the user + */ + public int getWheelRotation() { + return wheelRotation; + } +} diff --git a/awt/org/apache/harmony/awt/wtk/NativeEventQueue.java b/awt/org/apache/harmony/awt/wtk/NativeEventQueue.java new file mode 100644 index 0000000..0738cd1 --- /dev/null +++ b/awt/org/apache/harmony/awt/wtk/NativeEventQueue.java @@ -0,0 +1,117 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Mikhail Danilov + * @version $Revision$ + */ +package org.apache.harmony.awt.wtk; + +import java.util.LinkedList; + + +/** + * Describes the cross-platform native event queue interface + * + *

The implementation constructor should remember thread it was + * created. All other methods would be called obly from this thread, + * except awake(). + */ +public abstract class NativeEventQueue { + + private ShutdownWatchdog shutdownWatchdog; + private class EventMonitor {} + private final Object eventMonitor = new EventMonitor(); + private final LinkedList eventQueue = new LinkedList(); + + public static abstract class Task { + public volatile Object returnValue; + + public abstract void perform(); + } + + /** + * Blocks current thread until native event queue is not empty + * or awaken from other thread by awake(). + * + *

Should be called only on tread which + * will process native events. + * + * @return if event loop should be stopped + */ + public abstract boolean waitEvent(); + + /** + * Determines whether or not the native event queue is empty. + * An queue is empty if it contains no messages waiting. + * + * @return true if the queue is empty; false otherwise + */ + public boolean isEmpty() { + synchronized(eventQueue) { + return eventQueue.isEmpty(); + } + } + + public NativeEvent getNextEvent() { + synchronized (eventQueue) { + if (eventQueue.isEmpty()) { + shutdownWatchdog.setNativeQueueEmpty(true); + return null; + } + return eventQueue.remove(0); + } + } + + protected void addEvent(NativeEvent event) { + synchronized (eventQueue) { + eventQueue.add(event); + shutdownWatchdog.setNativeQueueEmpty(false); + } + synchronized (eventMonitor) { + eventMonitor.notify(); + } + } + + public final Object getEventMonitor() { + return eventMonitor; + } + + public abstract void awake(); + + /** + * Gets AWT system window ID. + * + * @return AWT system window ID + */ + public abstract long getJavaWindow(); + + /** + * Add NativeEvent to the queue + */ + public abstract void dispatchEvent(); + + public abstract void performTask(Task task); + + public abstract void performLater(Task task); + + public final void setShutdownWatchdog(ShutdownWatchdog watchdog) { + synchronized (eventQueue) { + shutdownWatchdog = watchdog; + } + } + +} diff --git a/awt/org/apache/harmony/awt/wtk/NativeEventThread.java b/awt/org/apache/harmony/awt/wtk/NativeEventThread.java new file mode 100644 index 0000000..d50add4 --- /dev/null +++ b/awt/org/apache/harmony/awt/wtk/NativeEventThread.java @@ -0,0 +1,78 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Pavel Dolgov + * @version $Revision$ + */ +package org.apache.harmony.awt.wtk; + + +/** + * NativeEventThread + */ +public class NativeEventThread extends Thread { + + public interface Init { + WTK init(); + } + + NativeEventQueue nativeQueue; + Init init; + + private WTK wtk; + + public NativeEventThread() { + super("AWT-NativeEventThread"); //$NON-NLS-1$ + setDaemon(true); + } + + @Override + public void run() { + synchronized (this) { + try { + wtk = init.init(); + nativeQueue = wtk.getNativeEventQueue(); + } finally { + notifyAll(); + } + } + + runModalLoop(); + } + + void runModalLoop() { + while (nativeQueue.waitEvent()) { + nativeQueue.dispatchEvent(); + } + } + + public void start(Init init) { + synchronized (this) { + this.init = init; + super.start(); + try { + wait(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + } + + public WTK getWTK() { + return wtk; + } +} diff --git a/awt/org/apache/harmony/awt/wtk/NativeIM.java b/awt/org/apache/harmony/awt/wtk/NativeIM.java new file mode 100644 index 0000000..1626f4a --- /dev/null +++ b/awt/org/apache/harmony/awt/wtk/NativeIM.java @@ -0,0 +1,130 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Dmitry A. Durnev + * @version $Revision$ + */ +package org.apache.harmony.awt.wtk; + +import java.awt.AWTEvent; +import java.awt.AWTException; +import java.awt.Image; +import java.awt.Rectangle; +import java.awt.im.spi.InputMethod; +import java.awt.im.spi.InputMethodContext; +import java.awt.im.spi.InputMethodDescriptor; +import java.lang.Character.Subset; +import java.util.Locale; + +/** + * A cross-platform interface for native input + * method sub-system functionality. + */ +public abstract class NativeIM implements InputMethod, InputMethodDescriptor { + protected InputMethodContext imc; + + public void activate() { + + } + + public void deactivate(boolean isTemporary) { + + } + + public void dispatchEvent(AWTEvent event) { + + } + + public void dispose() { + + } + + public void endComposition() { + + } + + public Object getControlObject() { + return null; + } + + public Locale getLocale() { + return null; + } + + public void hideWindows() { + + } + + public boolean isCompositionEnabled() { + return false; + } + + public void notifyClientWindowChange(Rectangle bounds) { + + } + + public void reconvert() { + + } + + public void removeNotify() { + + } + + public void setCharacterSubsets(Subset[] subsets) { + + } + + public void setCompositionEnabled(boolean enable) { + + } + + public void setInputMethodContext(InputMethodContext context) { + imc = context; + } + + public boolean setLocale(Locale locale) { + return false; + } + + public Locale[] getAvailableLocales() throws AWTException { + return new Locale[]{Locale.getDefault(), Locale.ENGLISH}; + //return new Locale[]{Locale.getDefault(), Locale.US}; + } + + public InputMethod createInputMethod() throws Exception { + return this; + } + + public String getInputMethodDisplayName(Locale inputLocale, + Locale displayLanguage) { + return "System input methods"; //$NON-NLS-1$ + } + + public Image getInputMethodIcon(Locale inputLocale) { + return null; + } + + public boolean hasDynamicLocaleList() { + return false; + } + + public abstract void disableIME(); + +// public abstract void disableIME(long id); + +} diff --git a/awt/org/apache/harmony/awt/wtk/NativeMouseInfo.java b/awt/org/apache/harmony/awt/wtk/NativeMouseInfo.java new file mode 100644 index 0000000..0696975 --- /dev/null +++ b/awt/org/apache/harmony/awt/wtk/NativeMouseInfo.java @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Dmitry A. Durnev + * @version $Revision$ + */ +package org.apache.harmony.awt.wtk; + +import java.awt.Point; + +/** + * The interface provides access to platform dependent functionality + * for classes java.awt.PointerInfo & java.awt.MouseInfo. + */ +public interface NativeMouseInfo { + + /** + * Returns the Point that represents + * the coordinates of the pointer on the screen. + */ + Point getLocation(); + + /** + * Returns the number of buttons on the mouse. + * If no mouse is installed returns -1. + */ + int getNumberOfButtons(); +} diff --git a/awt/org/apache/harmony/awt/wtk/NativeRobot.java b/awt/org/apache/harmony/awt/wtk/NativeRobot.java new file mode 100644 index 0000000..0b354d0 --- /dev/null +++ b/awt/org/apache/harmony/awt/wtk/NativeRobot.java @@ -0,0 +1,75 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Dmitry A. Durnev + * @version $Revision$ + */ +package org.apache.harmony.awt.wtk; + +import java.awt.Color; +import java.awt.Rectangle; +import java.awt.image.BufferedImage; + +/** + * A cross-platform interface for java.awt.Robot implementation + */ +public interface NativeRobot { + + /** + * @see java.awt.Robot#createScreenCapture(Rectangle) + * @param screenRect rectangle to capture in screen coordinates + * @return the captured image or null if + * capture failed. + */ + BufferedImage createScreenCapture(Rectangle screenRect); + + /** + * @see java.awt.Robot#getPixelColor(int, int) + */ + Color getPixel(int x, int y); + + /** + * Generate a native system keyboard input event. + * @param keycode A Java virtual key code + * @param press A key is pressed if true, released otherwise + * @see java.awt.Robot#keyPress(int) + * @throws IllegalArgumentException if keycode is invalid in the native system + */ + void keyEvent(int keycode, boolean press); + + /** + * Generate a native system mouse button(s) press or release event. + * @param buttons A mask of Java mouse button flags + * @param press buttons are pressed if true, released otherwise + * @see java.awt.Robot#mousePress(int) + */ + void mouseButton(int buttons, boolean press); + + /** + * Generate a native system mouse motion event. + * + * @see java.awt.Robot#mouseMove(int, int) + */ + void mouseMove(int x, int y); + + /** + * Generate a native system mouse wheel event. + * + * @see java.awt.Robot#mouseWheel(int) + */ + void mouseWheel(int wheelAmt); +} diff --git a/awt/org/apache/harmony/awt/wtk/NativeWindow.java b/awt/org/apache/harmony/awt/wtk/NativeWindow.java new file mode 100644 index 0000000..73fd6c0 --- /dev/null +++ b/awt/org/apache/harmony/awt/wtk/NativeWindow.java @@ -0,0 +1,220 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Mikhail Danilov + * @version $Revision$ + */ +package org.apache.harmony.awt.wtk; + +import java.awt.Image; +import java.awt.Insets; +import java.awt.Point; +import java.awt.Rectangle; + +import org.apache.harmony.awt.gl.MultiRectArea; + + +/** + * Provides cross-platform way to manipulate native window. + * + * Results of methods are reported through native messages. + */ +public interface NativeWindow { + /** + * Returns system id of the associated window + * @return HWND on Windows, xwindow on X + */ + long getId(); + + /** + * Shows/hides window + * @param v - new visibility + */ + void setVisible(boolean v); + + /** + * Means only size should be changed + */ + static final int BOUNDS_NOMOVE = 1; + + /** + * Means only position should be changed + */ + static final int BOUNDS_NOSIZE = 2; + + /** + * Tries to set desired window bounds. It's not gurantied the + * property will have the desired value. The value change + * should be reported by system event (as for other properties). + * + *

If child, position is relative to parent window. + * @param x - desired x + * @param y - desired y + * @param w - desired width + * @param h - desired height + * @param boundsMask - bitwise OR of BOUNDS_* constants. + * Governs the new bounds interpretation. + */ + void setBounds(int x, int y, int w, int h, int boundsMask); + + /** + * Returns last notified window bounds. This means the last bounds + * reported by system event. + * + *

If child, position is relative to parent window. + * @return last notified window bounds + */ + Rectangle getBounds(); + + /** + * Returns last notified insets. This means the last insets + * reported by system event. Insets are margins around client area + * ocupied by system provided decor, ususally border and titlebar. + * @return last notified insets + */ + Insets getInsets(); + + /** + * Enables/disables processing of input (key, mouse) event + * by window. If disabled input events are ignored. + * @param value - if enabled + */ + void setEnabled(boolean value); + + /** + * Sets the "focusable" window state. + * @param value - if true makes window focusable + */ + void setFocusable(boolean value); + + /** + * + * @return current focusable window state + */ + boolean isFocusable(); + + /** + * Tries to set application input focus to the window or clear + * current focus from focused window. + * + *

For toplevel windows it's not gurantied focus will land in + * desired window even if function returns true. Focus traversal should be tracked + * by processing system events. + * + * @param focus - if true sets focus, else clears focus + * @return if success + */ + boolean setFocus(boolean focus); + + /** + * Destroys the asscoiated window. + * Attempts to use it thereafter can result in + * unpredictable bechavior. + */ + void dispose(); + + /** + * Changes window Z-order to place this window under, If w is null + * places places this window on the top. Z-order is per parent. + * Toplevels a children of desktop in terms of Z-order. + * @param w - window to place under. + */ + void placeAfter(NativeWindow w); + + /** + * Places window on top of Z-order + */ + void toFront(); + + /** + * Places window on bottom of Z-order + */ + void toBack(); + + /** + * Makes the window resizable/not resizable by user + * @param value - if resizable + */ + void setResizable(boolean value); + + /** + * Sets the window caption + * @param title - caption text + */ + void setTitle(String title); + + /** + * Activate the mouse event capturing + */ + void grabMouse(); + + /** + * Deactivate mouse event capturing + */ + void ungrabMouse(); + + /** + * Set extended state for top-level window. + * + * @param state - new state, bitmask of ICONIFIED, MAXIMIZED_BOTH, etc. + */ + void setState(int state); + + /** + * Set the image to be displayed in the minimized icon for + * top-level [decorated] window. + * @param image the icon image to be displayed + */ + void setIconImage(Image image); + + /** + * Makes window top-most if value is true, + * non-topmost(normal) otherwise. + */ + void setAlwaysOnTop(boolean value); + + /** + * Set desired [top-level] window bounds when being in maximized state. + * Fields set to Integer.MAX_VALUE are ignored[system-supplied values are + * used instead] + */ + void setMaximizedBounds(Rectangle bounds); + + /** + * Get absolute position on the screen + */ + Point getScreenPos(); + + /** + * Set a window "packed" flag: + * the flag indicates that if insets change + * client area shouldn't be resized, but frame + * must be resized instead + */ + void setPacked(boolean packed); + + /** + * Make window an "input method window" by setting + * special window style, e. g. small title bar, no + * close, minimize/maximize buttons. For internal + * use by input method framework. + * + */ + void setIMStyle(); + + MultiRectArea getObscuredRegion(Rectangle part); +} diff --git a/awt/org/apache/harmony/awt/wtk/ShutdownThread.java b/awt/org/apache/harmony/awt/wtk/ShutdownThread.java new file mode 100644 index 0000000..701eb46 --- /dev/null +++ b/awt/org/apache/harmony/awt/wtk/ShutdownThread.java @@ -0,0 +1,83 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Michael Danilov, Pavel Dolgov + * @version $Revision$ + */ +package org.apache.harmony.awt.wtk; + +import org.apache.harmony.awt.internal.nls.Messages; + +public final class ShutdownThread extends Thread { + + public static final class Watchdog { + } + + public ShutdownThread() { + setName("AWT-Shutdown"); //$NON-NLS-1$ + setDaemon(false); + } + + private boolean shouldStop = false; + + @Override + public void run() { + synchronized (this) { + notifyAll(); // synchronize the startup + + while (true) { + try { + wait(); + } catch (InterruptedException e) { + } + + if (shouldStop) { + notifyAll(); // synchronize the shutdown + return; + } + } + } + } + + @Override + public void start() { + synchronized (this) { + super.start(); + try { + wait(); + } catch (InterruptedException e) { + // awt.26=Shutdown thread was interrupted while starting + throw new RuntimeException( + Messages.getString("awt.26")); //$NON-NLS-1$ + } + } + } + + public void shutdown() { + synchronized (this) { + shouldStop = true; + notifyAll(); + try { + wait(); + } catch (InterruptedException e) { + // awt.27=Shutdown thread was interrupted while stopping + throw new RuntimeException( + Messages.getString("awt.27")); //$NON-NLS-1$ + } + } + } +} diff --git a/awt/org/apache/harmony/awt/wtk/ShutdownWatchdog.java b/awt/org/apache/harmony/awt/wtk/ShutdownWatchdog.java new file mode 100644 index 0000000..6efa519 --- /dev/null +++ b/awt/org/apache/harmony/awt/wtk/ShutdownWatchdog.java @@ -0,0 +1,86 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Pavel Dolgov + * @version $Revision$ + */ +package org.apache.harmony.awt.wtk; + +/** + * Shutdown Watchdog + */ +public final class ShutdownWatchdog { + + private boolean nativeQueueEmpty = true; + private boolean awtQueueEmpty = true; + private boolean windowListEmpty = true; + + private boolean forcedShutdown = false; + + private ShutdownThread thread; + + public synchronized void setNativeQueueEmpty(boolean empty) { + nativeQueueEmpty = empty; + checkShutdown(); + } + + public synchronized void setAwtQueueEmpty(boolean empty) { + awtQueueEmpty = empty; + checkShutdown(); + } + + public synchronized void setWindowListEmpty(boolean empty) { + windowListEmpty = empty; + checkShutdown(); + } + + public synchronized void forceShutdown() { + forcedShutdown = true; + shutdown(); + } + + public synchronized void start() { + keepAlive(); + } + + private void checkShutdown() { + if (canShutdown()) { + shutdown(); + } else { + keepAlive(); + } + } + + private boolean canShutdown() { + return (nativeQueueEmpty && awtQueueEmpty && windowListEmpty) || + forcedShutdown; + } + + private void keepAlive() { + if (thread == null) { + thread = new ShutdownThread(); + thread.start(); + } + } + + private void shutdown() { + if (thread != null) { + thread.shutdown(); + thread = null; + } + } +} diff --git a/awt/org/apache/harmony/awt/wtk/Synchronizer.java b/awt/org/apache/harmony/awt/wtk/Synchronizer.java new file mode 100644 index 0000000..3eeaa0b --- /dev/null +++ b/awt/org/apache/harmony/awt/wtk/Synchronizer.java @@ -0,0 +1,200 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Mikhail Danilov + * @version $Revision$ + */ +package org.apache.harmony.awt.wtk; + +import java.util.Hashtable; +import java.util.LinkedList; + +import org.apache.harmony.awt.internal.nls.Messages; + +/** + * Class synchronizer is to protect AWT state integrity in multithreading environment. + * It is supposed to have a child class per native platform. + * The only instance is created on the first use of one of the core AWT classes. + * Registers WTK on the dispatch thread startup. + * It is just a special kind of mutex. + * + */ + +public class Synchronizer { + //TODO: think about java.util.concurrent use for faster blocking/awaking operations + //TODO: think about all synchronized methods. Is there need to synchronize everything? + + /** + * This field holds the counter of lock operation. + * To free synchronizer unlock method must be called $acquestCounter times. + * Equals to 0 when synchronizer is free. + */ + protected int acquestCounter; + + /** + * This field holds the owner of synchronizer. + * Owner of synchronizer is a last thread that successfully locked synchronizer and + * still havn't freed it. Equals to null when synchronizer is free. + */ + protected Thread owner; + + /** + * This field holds the wait queue. + * Wait queue is a queue where thread wait for synchronizer access. + * Empty when synchronizer is free. + */ + protected final LinkedList waitQueue = new LinkedList(); + + /** + * The event dispatch thread + */ + protected Thread dispatchThread; + + private final Hashtable storedStates = new Hashtable(); + + /** + * Acquire the lock for this synchronizer. Nested lock is supported. + * If the mutex is already locked by another thread, the current thread will be put + * into wait queue until the lock becomes available. + * All user threads are served in FIFO order. Dispatch thread has higher priority. + * Supposed to be used in Toolkit.lockAWT() only. + */ + public void lock() { + synchronized (this) { + Thread curThread = Thread.currentThread(); + + if (acquestCounter == 0) { + acquestCounter = 1; + owner = curThread; + } else { + if (owner == curThread) { + acquestCounter++; + } else { + if (curThread == dispatchThread) { + waitQueue.addFirst(curThread); + } else { + waitQueue.addLast(curThread); + } + try { + wait(); + } catch (InterruptedException e) { + if (owner != curThread) { + waitQueue.remove(curThread); + // awt.1F=Waiting for resource access thread interrupted not from unlock method. + throw new RuntimeException(Messages + .getString("awt.1F")); //$NON-NLS-1$ + } + } + } + } + } + } + + /** + * Release the lock for this synchronizer. + * If wait queue is not empty the first waiting thread acquires the lock. + * Supposed to be used in Toolkit.unlockAWT() only. + */ + public void unlock() { + synchronized (this) { + if (acquestCounter == 0) { + // awt.20=Can't unlock not locked resource. + throw new RuntimeException(Messages.getString("awt.20")); //$NON-NLS-1$ + } + if (owner != Thread.currentThread()) { + // awt.21=Not owner can't unlock resource. + throw new RuntimeException(Messages.getString("awt.21")); //$NON-NLS-1$ + } + + acquestCounter--; + if (acquestCounter == 0) { + if (waitQueue.size() > 0) { + acquestCounter = 1; + owner = waitQueue.removeFirst(); + owner.interrupt(); + } else { + owner = null; + } + } + } + } + + /** + * Stores state of this synchronizer and frees it. + * Supposed to be used in Toolkit.unsafeInvokeAndWaitUnderAWTLock() only in pair with + * lockAndRestoreState(). + * Do not call it directly. + */ + public void storeStateAndFree() { + synchronized (this) { + Thread curThread = Thread.currentThread(); + + if (owner != curThread) { + // awt.22=Not owner can't free resource. + throw new RuntimeException(Messages.getString("awt.22")); //$NON-NLS-1$ + } + if (storedStates.containsKey(curThread)) { + // awt.23=One thread can't store state several times in a row. + throw new RuntimeException(Messages.getString("awt.23")); //$NON-NLS-1$ + } + + storedStates.put(curThread, new Integer(acquestCounter)); + acquestCounter = 1; + unlock(); + } + } + + /** + * Locks this synchronizer and restores it's state. + * Supposed to be used in Toolkit.unsafeInvokeAndWaitUnderAWTLock() only in pair with + * storeStateAndFree(). + * Do not call it directly. + */ + public void lockAndRestoreState() { + synchronized (this) { + Thread curThread = Thread.currentThread(); + + if (owner == curThread) { + // awt.24=Owner can't overwrite resource state. Lock operations may be lost. + throw new RuntimeException( + Messages.getString("awt.24")); //$NON-NLS-1$ + } + if (!storedStates.containsKey(curThread)) { + // awt.25=No state stored for current thread. + throw new RuntimeException(Messages.getString("awt.25")); //$NON-NLS-1$ + } + + lock(); + acquestCounter = storedStates.get(curThread).intValue(); + storedStates.remove(curThread); + } + } + + /** + * Sets references to WTK and event dispatch thread. + * Called on toolkit startup. + * + * @param wtk - reference to WTK instance + * @param dispatchThread - reference to event dispatch thread + */ + public void setEnvironment(WTK wtk, Thread dispatchThread) { + synchronized (this) { + this.dispatchThread = dispatchThread; + } + } + +} diff --git a/awt/org/apache/harmony/awt/wtk/SystemProperties.java b/awt/org/apache/harmony/awt/wtk/SystemProperties.java new file mode 100644 index 0000000..6b59f0e --- /dev/null +++ b/awt/org/apache/harmony/awt/wtk/SystemProperties.java @@ -0,0 +1,59 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Pavel Dolgov + * @version $Revision$ + */ +package org.apache.harmony.awt.wtk; + +import java.awt.Font; +import java.awt.font.TextAttribute; +import java.awt.im.InputMethodHighlight; +import java.util.Map; + +/** + * NativeProperties + */ + +public interface SystemProperties { + + /** + * Get current value of a system color + * @param index - one of java.awt.SystemColor constants + * @return ARGB value of requested system color + */ + int getSystemColorARGB(int index); + + /** + * Get default font for GUI elements such as menus and buttons + * @return the font object + */ + Font getDefaultFont(); + + /** + * Fill the given Map with system properties + */ + void init(Map desktopProperties); + + /** + * Fills the given map with system-dependent visual text + * attributes for the abstract description + * of the given input method highlight + * @see java.awt.Toolkit.mapInputMethodHighlight() + */ + void mapInputMethodHighlight(InputMethodHighlight highlight, Map map); +} diff --git a/awt/org/apache/harmony/awt/wtk/WTK.java b/awt/org/apache/harmony/awt/wtk/WTK.java new file mode 100644 index 0000000..4162fbd --- /dev/null +++ b/awt/org/apache/harmony/awt/wtk/WTK.java @@ -0,0 +1,61 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Pavel Dolgov + * @version $Revision$ + */ +package org.apache.harmony.awt.wtk; + +import java.awt.GraphicsDevice; + + +public abstract class WTK { + + public abstract GraphicsFactory getGraphicsFactory(); + public abstract NativeEventQueue getNativeEventQueue(); + public abstract WindowFactory getWindowFactory(); + + /** + * Returns platform specific implementation of the interface + * org.apache.harmony.awt.wtk.CursorFactory. + * @return implementation of CursorFactory + */ + public abstract CursorFactory getCursorFactory(); + + /** + * Returns platform specific implementation of the interface + * org.apache.harmony.awt.wtk.NativeMouseInfo. + * @return implementation of NativeMouseInfo + */ + public abstract NativeMouseInfo getNativeMouseInfo(); + + public abstract SystemProperties getSystemProperties(); + + /** + * Returns platform specific implementation of the interface + * org.apache.harmony.awt.wtk.NativeRobot. + * @return implementation of NativeRobot + */ + public abstract NativeRobot getNativeRobot(GraphicsDevice screen); + + /** + * Returns platform specific implementation of the abstract + * class org.apache.harmony.awt.wtk.NativeIM. + * @return implementation of NativeIM + */ + public abstract NativeIM getNativeIM(); +} diff --git a/awt/org/apache/harmony/awt/wtk/WindowFactory.java b/awt/org/apache/harmony/awt/wtk/WindowFactory.java new file mode 100644 index 0000000..23604da --- /dev/null +++ b/awt/org/apache/harmony/awt/wtk/WindowFactory.java @@ -0,0 +1,85 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Mikhail Danilov + * @version $Revision$ + */ +package org.apache.harmony.awt.wtk; + +import java.awt.Dimension; +import java.awt.Point; + +/** + * Provides factory for NativeWindow + */ +public interface WindowFactory { + /** + * Creates and returns NativeWindow with desired + * creation params + * + * @param p - initial window properties + * @return created window + */ + NativeWindow createWindow(CreationParams p); + /** + * Create NativeWindow instance connected to existing native resource + * @param nativeWindowId - id of existing window + * @return created NativeWindow instance + */ + NativeWindow attachWindow(long nativeWindowId); + /** + * Returns NativeWindow instance if created by this instance of + * WindowFactory, otherwise null + * + * @param id - HWND on Windows xwindow on X + * @return NativeWindow or null if unknown + */ + NativeWindow getWindowById(long id); + /** + * Returns NativeWindow instance of the top-level window + * that contains a specified point and was + * created by this instance of WindowFactory + * @param p - Point to check + * @return NativeWindow or null if the point is + * not within a window created by this WindowFactory + */ + NativeWindow getWindowFromPoint(Point p); + + /** + * Returns whether native system supports the state for windows. + * This method tells whether the UI concept of, say, maximization or iconification is supported. + * It will always return false for "compound" states like Frame.ICONIFIED|Frame.MAXIMIZED_VERT. + * In other words, the rule of thumb is that only queries with a single frame state + * constant as an argument are meaningful. + * + * @param state - one of named frame state constants. + * @return true is this frame state is supported by this Toolkit implementation, false otherwise. + */ + boolean isWindowStateSupported(int state); + + /** + * @see org.apache.harmony.awt.ComponentInternals + */ + void setCaretPosition(int x, int y); + + /** + * Request size of arbitrary native window + * @param id - window ID + * @return window size + */ + Dimension getWindowSizeById(long id); +} \ No newline at end of file diff --git a/awt/org/apache/harmony/beans/internal/nls/Messages.java b/awt/org/apache/harmony/beans/internal/nls/Messages.java new file mode 100644 index 0000000..727c757 --- /dev/null +++ b/awt/org/apache/harmony/beans/internal/nls/Messages.java @@ -0,0 +1,132 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * THE FILE HAS BEEN AUTOGENERATED BY MSGTOOL TOOL. + * All changes made to this file manually will be overwritten + * if this tool runs again. Better make changes in the template file. + */ + +package org.apache.harmony.beans.internal.nls; + + +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.Locale; +import java.util.MissingResourceException; +import java.util.ResourceBundle; + +import org.apache.harmony.kernel.vm.VM; +import org.apache.harmony.luni.util.MsgHelp; + +/** + * This class retrieves strings from a resource bundle and returns them, + * formatting them with MessageFormat when required. + *

+ * It is used by the system classes to provide national language support, by + * looking up messages in the + * org.apache.harmony.beans.internal.nls.messages + * + * resource bundle. Note that if this file is not available, or an invalid key + * is looked up, or resource bundle support is not available, the key itself + * will be returned as the associated message. This means that the KEY + * should a reasonable human-readable (english) string. + * + */ +public class Messages { + + private static final String sResource = + "org.apache.harmony.beans.internal.nls.messages"; //$NON-NLS-1$ + + /** + * Retrieves a message which has no arguments. + * + * @param msg + * String the key to look up. + * @return String the message for that key in the system message bundle. + */ + static public String getString(String msg) { + return MsgHelp.getString(sResource, msg); + } + + /** + * Retrieves a message which takes 1 argument. + * + * @param msg + * String the key to look up. + * @param arg + * Object the object to insert in the formatted output. + * @return String the message for that key in the system message bundle. + */ + static public String getString(String msg, Object arg) { + return getString(msg, new Object[] { arg }); + } + + /** + * Retrieves a message which takes 1 integer argument. + * + * @param msg + * String the key to look up. + * @param arg + * int the integer to insert in the formatted output. + * @return String the message for that key in the system message bundle. + */ + static public String getString(String msg, int arg) { + return getString(msg, new Object[] { Integer.toString(arg) }); + } + + /** + * Retrieves a message which takes 1 character argument. + * + * @param msg + * String the key to look up. + * @param arg + * char the character to insert in the formatted output. + * @return String the message for that key in the system message bundle. + */ + static public String getString(String msg, char arg) { + return getString(msg, new Object[] { String.valueOf(arg) }); + } + + /** + * Retrieves a message which takes 2 arguments. + * + * @param msg + * String the key to look up. + * @param arg1 + * Object an object to insert in the formatted output. + * @param arg2 + * Object another object to insert in the formatted output. + * @return String the message for that key in the system message bundle. + */ + static public String getString(String msg, Object arg1, Object arg2) { + return getString(msg, new Object[] { arg1, arg2 }); + } + + /** + * Retrieves a message which takes several arguments. + * + * @param msg + * String the key to look up. + * @param args + * Object[] the objects to insert in the formatted output. + * @return String the message for that key in the system message bundle. + */ + static public String getString(String msg, Object[] args) { + return MsgHelp.getString(sResource, msg, args); + } +} diff --git a/awt/org/apache/harmony/beans/internal/nls/messages.properties b/awt/org/apache/harmony/beans/internal/nls/messages.properties new file mode 100644 index 0000000..72b1c8c --- /dev/null +++ b/awt/org/apache/harmony/beans/internal/nls/messages.properties @@ -0,0 +1,103 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# messages for EN locale +beans.00=no getter for {0} property +beans.01=no property for name {0} is found +beans.02=in DefaultPersistenceDelegate.mutatesTo() {0} : {1} +beans.03=Target Bean class is null +beans.04=bad property name +beans.05=Modifier for setter method should be public. +beans.06=Number of parameters in setter method is not equal to 1. +beans.07=Parameter type in setter method does not corresponds to predefined. +beans.08=Number of parameters in getter method is not equal to 0. +beans.09=Parameter type in getter method does not corresponds to predefined. +beans.0A=Modifier for getter method should be public. +beans.0B=Exception in command execution +beans.0C=source is null +beans.0D=Error in expression: {0} +beans.0E=Changes are null +beans.0F=The new BeanContext can not be set +beans.10=no node is found for statement with target = {0} +beans.11=no getter for property {0} found +beans.12=cannot access property {0} getter +beans.13=no setter for property {0} found +beans.14=Exception while finding property descriptor +beans.15=The listener is null +beans.16=The provider is null +beans.17=The child is null +beans.18=The requestor is null +beans.19=The service class is null +beans.1A=The service selector is null +beans.1B=The service is null +beans.1C=The event is null +beans.1D=bean is null +beans.1E=Illegal class name: {0} +beans.1F=Method not found: get{0} +beans.20=Method not found: set{0} +beans.21=Modifier for indexed getter method should be public. +beans.22=Number of parameters in getter method is not equal to 1. +beans.23=Parameter in indexed getter method is not of integer type. +beans.24=Parameter type in indexed getter method does not correspond to predefined. +beans.25=Modifier for indexed setter method should be public. +beans.26=Number of parameters in indexed setter method is not equal to 2. +beans.27=First parameter type in indexed setter method should be int. +beans.28=Second parameter type in indexed setter method does not corresponds to predefined. +beans.29=Membership listener is null +beans.2A=Target child can not be null +beans.2B=Resource name can not be null +beans.2C=The child can not be null +beans.2D=Invalid resource +beans.2E=PropertyVetoException was thrown while removing a child: {0}; Original error message:{1} +beans.2F=Target child is null +beans.30=PropertyVetoException was thrown while adding a child: {0}; Original error message:{1} +beans.31=No valid method {0} for {1} found. +beans.32=Cannot acquire event type from {0} listener. +beans.33={0} does not return +beans.34={0} should have a single input parameter +beans.35=Single parameter does not match to {0} class +beans.36=No input params are allowed for getListenerMethod +beans.37=Return type of getListenerMethod is not an array of listeners +beans.38=Add and remove methods are not available +beans.39=Cannot generate event set descriptor for name {0}. +beans.3A=Event type with name {0} is not found. +beans.3B=skipping expression {0}... +beans.3C=Unknown method name for array +beans.3D=First parameter in array getter(setter) is not of Integer type +beans.3E=Illegal number of arguments in array getter +beans.3F=Illegal number of arguments in array setter +beans.40=No constructor for class {0} found +beans.41=No method with name {0} is found +beans.42=target is not generated: classname {0} is not found +beans.43=Cannot convert {0} to char +beans.44=for property {0} no getter(setter) is found +beans.45=method name is not generated: error in getMethodName() +beans.46=Not a valid child +beans.47=Unable to instantiate property editor +beans.48=Property editor is not assignable from the PropertyEditor interface +beans.49=Child cannot implement both BeanContextChild and BeanContextProxy +beans.4A=newInstance is null +beans.4B=type is null +beans.4C=encoder is null +beans.4D=Invalid method call +beans.4E=stopClass is not ancestor of beanClass +beans.4F=search path is null +beans.50=not an indexed property +beans.51=Listener method {0} should have parameter of type {1} +beans.52=listenerMethodName(s) is null +beans.53=eventSetName is null +beans.54=listenerType is null +beans.55=Method is null diff --git a/awt/org/apache/harmony/x/imageio/internal/nls/Messages.java b/awt/org/apache/harmony/x/imageio/internal/nls/Messages.java new file mode 100644 index 0000000..498e1bb --- /dev/null +++ b/awt/org/apache/harmony/x/imageio/internal/nls/Messages.java @@ -0,0 +1,124 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * THE FILE HAS BEEN AUTOGENERATED BY MSGTOOL TOOL. + * All changes made to this file manually will be overwritten + * if this tool runs again. Better make changes in the template file. + */ + +package org.apache.harmony.x.imageio.internal.nls; + +import org.apache.harmony.luni.util.MsgHelp; + +/** + * This class retrieves strings from a resource bundle and returns them, + * formatting them with MessageFormat when required. + *

+ * It is used by the system classes to provide national language support, by + * looking up messages in the + * org.apache.harmony.x.imageio.internal.nls.messages + * + * resource bundle. Note that if this file is not available, or an invalid key + * is looked up, or resource bundle support is not available, the key itself + * will be returned as the associated message. This means that the KEY + * should a reasonable human-readable (english) string. + * + */ +public class Messages { + + private static final String sResource = + "org.apache.harmony.x.imageio.internal.nls.messages"; //$NON-NLS-1$ + + /** + * Retrieves a message which has no arguments. + * + * @param msg + * String the key to look up. + * @return String the message for that key in the system message bundle. + */ + static public String getString(String msg) { + return MsgHelp.getString(sResource, msg); + } + + /** + * Retrieves a message which takes 1 argument. + * + * @param msg + * String the key to look up. + * @param arg + * Object the object to insert in the formatted output. + * @return String the message for that key in the system message bundle. + */ + static public String getString(String msg, Object arg) { + return getString(msg, new Object[] { arg }); + } + + /** + * Retrieves a message which takes 1 integer argument. + * + * @param msg + * String the key to look up. + * @param arg + * int the integer to insert in the formatted output. + * @return String the message for that key in the system message bundle. + */ + static public String getString(String msg, int arg) { + return getString(msg, new Object[] { Integer.toString(arg) }); + } + + /** + * Retrieves a message which takes 1 character argument. + * + * @param msg + * String the key to look up. + * @param arg + * char the character to insert in the formatted output. + * @return String the message for that key in the system message bundle. + */ + static public String getString(String msg, char arg) { + return getString(msg, new Object[] { String.valueOf(arg) }); + } + + /** + * Retrieves a message which takes 2 arguments. + * + * @param msg + * String the key to look up. + * @param arg1 + * Object an object to insert in the formatted output. + * @param arg2 + * Object another object to insert in the formatted output. + * @return String the message for that key in the system message bundle. + */ + static public String getString(String msg, Object arg1, Object arg2) { + return getString(msg, new Object[] { arg1, arg2 }); + } + + /** + * Retrieves a message which takes several arguments. + * + * @param msg + * String the key to look up. + * @param args + * Object[] the objects to insert in the formatted output. + * @return String the message for that key in the system message bundle. + */ + static public String getString(String msg, Object[] args) { + return MsgHelp.getString(sResource, msg, args); + } +} diff --git a/awt/org/apache/harmony/x/imageio/internal/nls/messages.properties b/awt/org/apache/harmony/x/imageio/internal/nls/messages.properties new file mode 100644 index 0000000..8a49dd8 --- /dev/null +++ b/awt/org/apache/harmony/x/imageio/internal/nls/messages.properties @@ -0,0 +1,18 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# messages for EN locale +imageio.1=Wrong bitDepth-numBands composition \ No newline at end of file diff --git a/awt/org/apache/harmony/x/imageio/metadata/IIOMetadataUtils.java b/awt/org/apache/harmony/x/imageio/metadata/IIOMetadataUtils.java new file mode 100644 index 0000000..caeefdd5 --- /dev/null +++ b/awt/org/apache/harmony/x/imageio/metadata/IIOMetadataUtils.java @@ -0,0 +1,94 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package org.apache.harmony.x.imageio.metadata; + +import java.lang.reflect.Method; +import java.security.AccessController; +import java.security.PrivilegedAction; + +import javax.imageio.metadata.IIOMetadataFormat; +import javax.imageio.metadata.IIOMetadataFormatImpl; + +public class IIOMetadataUtils { + private IIOMetadataUtils() {} + + public static IIOMetadataFormat instantiateMetadataFormat( + String formatName, boolean standardFormatSupported, + String nativeMetadataFormatName, String nativeMetadataFormatClassName, + String [] extraMetadataFormatNames, String [] extraMetadataFormatClassNames + ) { + if (formatName == null) { + throw new IllegalArgumentException("formatName == null!"); + } + if (formatName.equals(IIOMetadataFormatImpl.standardMetadataFormatName)) { + if (standardFormatSupported) { + return IIOMetadataFormatImpl.getStandardFormatInstance(); + } + } + + String className = null; + + if (formatName.equals(nativeMetadataFormatName)) { + className = nativeMetadataFormatClassName; + } else if (extraMetadataFormatNames != null) { + for (int i = 0; i < extraMetadataFormatNames.length; i++) { + if (formatName.equals(extraMetadataFormatNames[i])) { + className = extraMetadataFormatClassNames[i]; + break; + } + } + } + + if (className == null) { + throw new IllegalArgumentException("Unsupported format name"); + } + + // Get the context class loader and try to use it first + ClassLoader contextClassloader = AccessController.doPrivileged( + new PrivilegedAction() { + public ClassLoader run() { + return Thread.currentThread().getContextClassLoader(); + } + }); + + Class cls; + + try { + cls = Class.forName(className, true, contextClassloader); + } catch (ClassNotFoundException e) { + try { + // Use current class loader + cls = Class.forName(className); + } catch (ClassNotFoundException e1) { + throw new IllegalStateException ("Can't obtain format"); + } + } + + try { + //???AWT: + //Method getInstance = cls.getMethod("getInstance"); + //return (IIOMetadataFormat) getInstance.invoke(null); + return null; + } catch (Exception e) { + IllegalStateException e1 = new IllegalStateException("Can't obtain format"); + e1.initCause(e); // Add some details to the message + throw e1; + } + } +} diff --git a/awt/org/apache/harmony/x/imageio/plugins/jpeg/IISDecodingImageSource.java b/awt/org/apache/harmony/x/imageio/plugins/jpeg/IISDecodingImageSource.java new file mode 100644 index 0000000..051f906 --- /dev/null +++ b/awt/org/apache/harmony/x/imageio/plugins/jpeg/IISDecodingImageSource.java @@ -0,0 +1,115 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Rustem Rafikov + * @version $Revision: 1.2 $ + */ +package org.apache.harmony.x.imageio.plugins.jpeg; + +import javax.imageio.stream.ImageInputStream; + +import org.apache.harmony.awt.gl.image.DecodingImageSource; + +import java.io.InputStream; +import java.io.IOException; + +/** + * This allows usage of the java2d jpegdecoder with ImageInputStream in + * the JPEGImageReader. Temporary, only to make JPEGImageReader#read(..) + * working. + * + */ +public class IISDecodingImageSource extends DecodingImageSource { + + private final InputStream is; + + public IISDecodingImageSource(ImageInputStream iis) { + is = new IISToInputStreamWrapper(iis); + } + + @Override + protected boolean checkConnection() { + return true; + } + + @Override + protected InputStream getInputStream() { + return is; + } + + static class IISToInputStreamWrapper extends InputStream { + + private ImageInputStream input; + + public IISToInputStreamWrapper(ImageInputStream input) { + this.input=input; + } + + @Override + public int read() throws IOException { + return input.read(); + } + + @Override + public int read(byte[] b) throws IOException { + return input.read(b); + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + return input.read(b, off, len); + } + + @Override + public long skip(long n) throws IOException { + return input.skipBytes(n); + } + + @Override + public boolean markSupported() { + return true; // This is orig + + // ???AWT: FIXME + // This is an error in Harmony. Not all input streams + // have mark support and it is not ok to just return true. + // There should be an input.markSupported(). However, if + // this call returns false, nothing works anymore. + + // The backside is that BitmapFactory uses a call to markSupport() + // to find out if it needs to warp the stream in a + // BufferedInputStream to get mark support, and this fails! + + // Currently, the hack is in BitmapFactory, where we always + // wrap the stream in a BufferedInputStream. + } + + @Override + public void mark(int readlimit) { + input.mark(); + } + + @Override + public void reset() throws IOException { + input.reset(); + } + + @Override + public void close() throws IOException { + input.close(); + } + } +} diff --git a/awt/org/apache/harmony/x/imageio/plugins/jpeg/JPEGConsts.java b/awt/org/apache/harmony/x/imageio/plugins/jpeg/JPEGConsts.java new file mode 100644 index 0000000..067a825 --- /dev/null +++ b/awt/org/apache/harmony/x/imageio/plugins/jpeg/JPEGConsts.java @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Rustem V. Rafikov + * @version $Revision: 1.2 $ + */ +package org.apache.harmony.x.imageio.plugins.jpeg; + +public class JPEGConsts { + + private JPEGConsts() {} + + public static final int SOI = 0xD8; + + //-- IJG (Independed JPEG Group) color spaces + public static final int JCS_UNKNOW = 0; + public static final int JCS_GRAYSCALE = 1; + public static final int JCS_RGB = 2; + public static final int JCS_YCbCr = 3; + public static final int JCS_CMYK = 4; + public static final int JCS_YCC = 5; + public static final int JCS_RGBA = 6; + public static final int JCS_YCbCrA = 7; + public static final int JCS_YCCA = 10; + public static final int JCS_YCCK = 11; + + public static int[][] BAND_OFFSETS = {{}, {0}, {0, 1}, {0, 1, 2}, {0, 1, 2, 3}}; + + public static final float DEFAULT_JPEG_COMPRESSION_QUALITY = 0.75f; +} diff --git a/awt/org/apache/harmony/x/imageio/plugins/jpeg/JPEGImageReader.java b/awt/org/apache/harmony/x/imageio/plugins/jpeg/JPEGImageReader.java new file mode 100644 index 0000000..110ed23 --- /dev/null +++ b/awt/org/apache/harmony/x/imageio/plugins/jpeg/JPEGImageReader.java @@ -0,0 +1,126 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Rustem V. Rafikov + * @version $Revision: 1.4 $ + */ +package org.apache.harmony.x.imageio.plugins.jpeg; + + +import javax.imageio.ImageReader; +import javax.imageio.ImageReadParam; +import javax.imageio.ImageTypeSpecifier; +import javax.imageio.plugins.jpeg.JPEGImageReadParam; +import javax.imageio.stream.ImageInputStream; +import javax.imageio.metadata.IIOMetadata; +import javax.imageio.spi.ImageReaderSpi; + +import org.apache.harmony.awt.gl.image.DecodingImageSource; +import org.apache.harmony.awt.gl.image.OffscreenImage; + +import java.io.IOException; +import java.util.Iterator; +import java.awt.image.BufferedImage; + +/** + * This implementation uses org.apache.harmony.awt.gl.image.JpegDecoder to read + * an image. The only implemented method is read(..); + * + * TODO: Implements generic decoder to be used by javad2 and imageio + * + * @see org.apache.harmony.awt.gl.image.JpegDecoder + * @see org.apache.harmony.x.imageio.plugins.jpeg.IISDecodingImageSource + */ +public class JPEGImageReader extends ImageReader { + + ImageInputStream iis; + + public JPEGImageReader(ImageReaderSpi imageReaderSpi) { + super(imageReaderSpi); + } + + @Override + public int getHeight(int i) throws IOException { + //-- TODO imlement + throw new UnsupportedOperationException("not implemented yet"); + } + + @Override + public int getWidth(int i) throws IOException { + //-- TODO imlement + throw new UnsupportedOperationException("not implemented yet"); + } + + @Override + public int getNumImages(boolean b) throws IOException { + //-- TODO imlement + throw new UnsupportedOperationException("not implemented yet"); + } + + @Override + public Iterator getImageTypes(int i) throws IOException { + //-- TODO imlement + throw new UnsupportedOperationException("not implemented yet"); + } + + @Override + public IIOMetadata getStreamMetadata() throws IOException { + //-- TODO imlement + throw new UnsupportedOperationException("not implemented yet"); + } + + @Override + public IIOMetadata getImageMetadata(int i) throws IOException { + //-- TODO imlement + throw new UnsupportedOperationException("not implemented yet"); + } + + @Override + public BufferedImage read(int i, ImageReadParam imageReadParam) throws IOException { + if (iis == null) { + throw new IllegalArgumentException("input stream == null"); + } + + DecodingImageSource source = new IISDecodingImageSource(iis); + OffscreenImage image = new OffscreenImage(source); + source.addConsumer(image); + source.load(); + // The interrupted flag should be cleared because ImageDecoder interrupts + // current thread while decoding. The same technique is used in + // ImageLoader#run(). Another solution can be to create + // a separate decoding thread. However, decoder keeps its own pool + // of threads so creating a new thread will be just a waste of resources. + Thread.interrupted(); + return image.getBufferedImage(); + } + + @Override + public BufferedImage read(int i) throws IOException { + return read(i, null); + } + + @Override + public void setInput(Object input, boolean seekForwardOnly, boolean ignoreMetadata) { + super.setInput(input, seekForwardOnly, ignoreMetadata); + iis = (ImageInputStream) input; + } + + @Override + public ImageReadParam getDefaultReadParam() { + return new JPEGImageReadParam(); + } +} diff --git a/awt/org/apache/harmony/x/imageio/plugins/jpeg/JPEGImageReaderSpi.java b/awt/org/apache/harmony/x/imageio/plugins/jpeg/JPEGImageReaderSpi.java new file mode 100644 index 0000000..c719ce7 --- /dev/null +++ b/awt/org/apache/harmony/x/imageio/plugins/jpeg/JPEGImageReaderSpi.java @@ -0,0 +1,86 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Rustem V. Rafikov + * @version $Revision: 1.3 $ + */ +package org.apache.harmony.x.imageio.plugins.jpeg; + +import java.io.IOException; +import java.util.Locale; +import javax.imageio.ImageReader; +import javax.imageio.spi.ImageReaderSpi; +import javax.imageio.spi.ServiceRegistry; +import javax.imageio.stream.ImageInputStream; + +public class JPEGImageReaderSpi extends ImageReaderSpi { + + public JPEGImageReaderSpi() { + super(JPEGSpiConsts.vendorName, JPEGSpiConsts.version, + JPEGSpiConsts.names, JPEGSpiConsts.suffixes, + JPEGSpiConsts.MIMETypes, JPEGSpiConsts.readerClassName, + STANDARD_INPUT_TYPE, JPEGSpiConsts.writerSpiNames, + JPEGSpiConsts.supportsStandardStreamMetadataFormat, + JPEGSpiConsts.nativeStreamMetadataFormatName, + JPEGSpiConsts.nativeStreamMetadataFormatClassName, + JPEGSpiConsts.extraStreamMetadataFormatNames, + JPEGSpiConsts.extraStreamMetadataFormatClassNames, + JPEGSpiConsts.supportsStandardImageMetadataFormat, + JPEGSpiConsts.nativeImageMetadataFormatName, + JPEGSpiConsts.nativeImageMetadataFormatClassName, + JPEGSpiConsts.extraImageMetadataFormatNames, + JPEGSpiConsts.extraImageMetadataFormatClassNames); + } + + + @Override + public boolean canDecodeInput(Object source) throws IOException { + ImageInputStream markable = (ImageInputStream) source; + try { + markable.mark(); + + byte[] signature = new byte[3]; + markable.seek(0); + markable.read(signature, 0, 3); + markable.reset(); + + if ((signature[0] & 0xFF) == 0xFF && + (signature[1] & 0xFF) == JPEGConsts.SOI && + (signature[2] & 0xFF) == 0xFF) { // JPEG + return true; + } + } catch (IOException e) { + e.printStackTrace(); + } + return false; + } + + @Override + public ImageReader createReaderInstance(Object extension) throws IOException { + return new JPEGImageReader(this); + } + + @Override + public String getDescription(Locale locale) { + return "DRL JPEG decoder"; + } + + @Override + public void onRegistration(ServiceRegistry registry, Class category) { + // super.onRegistration(registry, category); + } +} diff --git a/awt/org/apache/harmony/x/imageio/plugins/jpeg/JPEGImageWriter.java b/awt/org/apache/harmony/x/imageio/plugins/jpeg/JPEGImageWriter.java new file mode 100644 index 0000000..ae3e876 --- /dev/null +++ b/awt/org/apache/harmony/x/imageio/plugins/jpeg/JPEGImageWriter.java @@ -0,0 +1,402 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Rustem V. Rafikov + * @version $Revision: 1.3 $ + */ +package org.apache.harmony.x.imageio.plugins.jpeg; + +import com.android.internal.awt.ImageOutputStreamWrapper; + +import javax.imageio.ImageWriter; +import javax.imageio.IIOImage; +import javax.imageio.ImageTypeSpecifier; +import javax.imageio.ImageWriteParam; +import javax.imageio.plugins.jpeg.JPEGImageWriteParam; +import javax.imageio.stream.ImageOutputStream; +import javax.imageio.spi.ImageWriterSpi; +import javax.imageio.metadata.IIOMetadata; + +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Bitmap.CompressFormat; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.awt.image.*; +import java.awt.*; +import java.awt.color.ColorSpace; + +/** + * @author Rustem V. Rafikov + * @version $Revision: 1.3 $ + */ +public class JPEGImageWriter extends ImageWriter { + + // /* ???AWT: Debugging + private static final boolean DEBUG = false; + private static Bitmap bm; + public static Bitmap getBitmap() { + return bm; + } + private static BufferedImage bufImg; + public static BufferedImage getBufImage() { + return bufImg; + } + static private RenderedImage renImg; + static public RenderedImage getRenImage() { + return renImg; + } + // */ + + private long cinfo; + private RenderedImage image; + private Raster sourceRaster; + private WritableRaster scanRaster; + private int srcXOff = 0; + private int srcYOff = 0; + private int srcWidth; + private int srcHeight; + + //-- y step for image subsampling + private int deltaY = 1; + //-- x step for image subsampling + private int deltaX = 1; + + private ImageOutputStream ios; + + public JPEGImageWriter(ImageWriterSpi imageWriterSpi) { + super(imageWriterSpi); + //???AWT: cinfo = initCompressionObj(); + cinfo = System.currentTimeMillis(); + } + + static { + //???AWT + /* + System.loadLibrary("jpegencoder"); + initWriterIds(ImageOutputStream.class); + */ + } + + @Override + public void write(IIOMetadata iioMetadata, IIOImage iioImage, ImageWriteParam param) + throws IOException { + + if (ios == null) { + throw new IllegalArgumentException("ios == null"); + } + if (iioImage == null) { + throw new IllegalArgumentException("Image equals null"); + } + + RenderedImage img = null; + if (!iioImage.hasRaster()) { + img = iioImage.getRenderedImage(); + if (img instanceof BufferedImage) { + sourceRaster = ((BufferedImage) img).getRaster(); + } else { + sourceRaster = img.getData(); + } + } else { + sourceRaster = iioImage.getRaster(); + } + + // AWT???: Debugging + if (DEBUG) { + if( img==null ) { + System.out.println("****J: Image is NULL"); + } else { + renImg = img; + bufImg = (BufferedImage)img; + } + } + + int numBands = sourceRaster.getNumBands(); + int sourceIJGCs = img == null ? JPEGConsts.JCS_UNKNOW : getSourceCSType(img); + + srcWidth = sourceRaster.getWidth(); + srcHeight = sourceRaster.getHeight(); + + int destWidth = srcWidth; + int destHeight = srcHeight; + + boolean progressive = false; + + if (param != null) { + Rectangle reg = param.getSourceRegion(); + if (reg != null) { + srcXOff = reg.x; + srcYOff = reg.y; + + srcWidth = reg.width + srcXOff > srcWidth + ? srcWidth - srcXOff + : reg.width; + srcHeight = reg.height + srcYOff > srcHeight + ? srcHeight - srcYOff + : reg.height; + } + + //-- TODO uncomment when JPEGImageWriteParam be implemented + //-- Only default progressive mode yet + // progressive = param.getProgressiveMode() == ImageWriteParam.MODE_DEFAULT; + + //-- def is 1 + deltaX = param.getSourceXSubsampling(); + deltaY = param.getSourceYSubsampling(); + + //-- def is 0 + int offsetX = param.getSubsamplingXOffset(); + int offsetY = param.getSubsamplingYOffset(); + + srcXOff += offsetX; + srcYOff += offsetY; + srcWidth -= offsetX; + srcHeight -= offsetY; + + destWidth = (srcWidth + deltaX - 1) / deltaX; + destHeight = (srcHeight + deltaY - 1) / deltaY; + } + + //-- default DQTs (see JPEGQTable java doc and JPEG spec K1 & K2 tables) + //-- at http://www.w3.org/Graphics/JPEG/itu-t81.pdf + //-- Only figuring out how to set DQT in IJG library for future metadata + //-- support. IJG def tables are the same. + //JPEGQTable[] dqt = new JPEGQTable[2]; +// int[][] dqt = null; +// int[][] dqt = new int[2][]; +// dqt[0] = JPEGQTable.K1Div2Luminance.getTable(); +// dqt[1] = JPEGQTable.K2Div2Chrominance.getTable(); + + //???AWT: I think we don't need this amymore + /* + //-- using default color space + //-- TODO: Take destination cs from param or use default if there is no cs + int destIJGCs = img == null ? JPEGConsts.JCS_UNKNOW : getDestinationCSType(img); + + DataBufferByte dbuffer = new DataBufferByte(numBands * srcWidth); + + scanRaster = Raster.createInterleavedRaster(dbuffer, srcWidth, 1, + numBands * srcWidth, numBands, JPEGConsts.BAND_OFFSETS[numBands], null); + + encode(dbuffer.getData(), srcWidth, destWidth, destHeight, deltaX, + sourceIJGCs, destIJGCs, numBands, progressive, + null, cinfo); + */ + + SampleModel model = sourceRaster.getSampleModel(); + + if (model instanceof SinglePixelPackedSampleModel) { + DataBufferInt ibuf = (DataBufferInt)sourceRaster.getDataBuffer(); + int[] pixels = ibuf.getData(); + + // Create a bitmap with the pixel + bm = Bitmap.createBitmap(pixels, srcWidth, srcHeight, Bitmap.Config.ARGB_8888); + + // Use Bitmap.compress() to write the image + ImageOutputStreamWrapper iosw = new ImageOutputStreamWrapper(ios); + bm.compress(CompressFormat.JPEG, 100, iosw); + } else { + // ???AWT: Add support for other color models + throw new RuntimeException("Color model not supported yet"); + } + + } + + @Override + public void dispose() { + super.dispose(); + if (cinfo != 0) { + //???AWT: dispose(cinfo); + cinfo = 0; + ios = null; + } + } + + + public IIOMetadata getDefaultStreamMetadata(ImageWriteParam imageWriteParam) { + throw new UnsupportedOperationException("not supported yet"); + } + + public IIOMetadata getDefaultImageMetadata(ImageTypeSpecifier imageTypeSpecifier, ImageWriteParam imageWriteParam) { + throw new UnsupportedOperationException("not supported yet"); + } + + @Override + public IIOMetadata convertStreamMetadata(IIOMetadata iioMetadata, ImageWriteParam imageWriteParam) { + throw new UnsupportedOperationException("not supported yet"); + } + + @Override + public IIOMetadata convertImageMetadata(IIOMetadata iioMetadata, ImageTypeSpecifier imageTypeSpecifier, ImageWriteParam imageWriteParam) { + throw new UnsupportedOperationException("not supported yet"); + } + + @Override + public void setOutput(Object output) { + super.setOutput(output); + ios = (ImageOutputStream) output; + //???AWT: setIOS(ios, cinfo); + sourceRaster = null; + scanRaster = null; + srcXOff = 0; + srcYOff = 0; + srcWidth = 0; + srcHeight = 0; + deltaY = 1; + } + + /** + * Frees resources + * @param structPointer + */ + //???AWT: private native void dispose(long structPointer); + + /** + * Inits methods Ids for native to java callbacks + * @param iosClass + */ + //???AWT: private native static void initWriterIds(Class iosClass); + + /** + * Inits compression objects + * @return pointer to the native structure + */ + //???AWT: private native long initCompressionObj(); + + /** + * Sets image output stream in IJG layer + * @param stream + */ + //???AWT: private native void setIOS(ImageOutputStream stream, long structPointer); + + /** + * Runs encoding process. + * + * @param data image data buffer to encode + * @param srcWidth - source width + * @param width - destination width + * @param height destination height + * @param deltaX - x subsampling step + * @param inColorSpace - original color space + * @param outColorSpace - destination color space + * @param numBands - number of bands + * @param cinfo - native handler + * @return + */ + //???AWT: + /* + private native boolean encode(byte[] data, int srcWidth, + int width, int height, int deltaX, + int inColorSpace, int outColorSpace, + int numBands, boolean progressive, + int[][] dqt, + long cinfo); + */ + + /** + * Callback for getting a next scanline + * @param scanline scan line number + */ + @SuppressWarnings("unused") + private void getScanLine(int scanline) { + //-- TODO: processImageProgress in ImageWriter + Raster child = sourceRaster.createChild(srcXOff, + srcYOff + scanline * deltaY, srcWidth, 1, 0, 0, null); + + scanRaster.setRect(child); + } + + /** + * Maps color space types to IJG color spaces + * @param image + * @return + */ + private int getSourceCSType(RenderedImage image) { + int type = JPEGConsts.JCS_UNKNOW; + ColorModel cm = image.getColorModel(); + + if (null == cm) { + return type; + } + + if (cm instanceof IndexColorModel) { + throw new UnsupportedOperationException("IndexColorModel is not supported yet"); + } + + boolean hasAlpha = cm.hasAlpha(); + ColorSpace cs = cm.getColorSpace(); + switch(cs.getType()) { + case ColorSpace.TYPE_GRAY: + type = JPEGConsts.JCS_GRAYSCALE; + break; + case ColorSpace.TYPE_RGB: + type = hasAlpha ? JPEGConsts.JCS_RGBA : JPEGConsts.JCS_RGB; + break; + case ColorSpace.TYPE_YCbCr: + type = hasAlpha ? JPEGConsts.JCS_YCbCrA : JPEGConsts.JCS_YCbCr; + break; + case ColorSpace.TYPE_3CLR: + type = hasAlpha ? JPEGConsts.JCS_YCCA : JPEGConsts.JCS_YCC; + break; + case ColorSpace.TYPE_CMYK: + type = JPEGConsts.JCS_CMYK; + break; + } + return type; + } + + /** + * Returns destination color space. + * (YCbCr[A] for RGB) + * + * @param image + * @return + */ + private int getDestinationCSType(RenderedImage image) { + int type = JPEGConsts.JCS_UNKNOW; + ColorModel cm = image.getColorModel(); + if (null != cm) { + boolean hasAlpha = cm.hasAlpha(); + ColorSpace cs = cm.getColorSpace(); + + switch(cs.getType()) { + case ColorSpace.TYPE_GRAY: + type = JPEGConsts.JCS_GRAYSCALE; + break; + case ColorSpace.TYPE_RGB: + type = hasAlpha ? JPEGConsts.JCS_YCbCrA : JPEGConsts.JCS_YCbCr; + break; + case ColorSpace.TYPE_YCbCr: + type = hasAlpha ? JPEGConsts.JCS_YCbCrA : JPEGConsts.JCS_YCbCr; + break; + case ColorSpace.TYPE_3CLR: + type = hasAlpha ? JPEGConsts.JCS_YCCA : JPEGConsts.JCS_YCC; + break; + case ColorSpace.TYPE_CMYK: + type = JPEGConsts.JCS_CMYK; + break; + } + } + return type; + } + + public ImageWriteParam getDefaultWriteParam() { + return new JPEGImageWriteParam(getLocale()); + } +} diff --git a/awt/org/apache/harmony/x/imageio/plugins/jpeg/JPEGImageWriterSpi.java b/awt/org/apache/harmony/x/imageio/plugins/jpeg/JPEGImageWriterSpi.java new file mode 100644 index 0000000..b7990e0 --- /dev/null +++ b/awt/org/apache/harmony/x/imageio/plugins/jpeg/JPEGImageWriterSpi.java @@ -0,0 +1,56 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Rustem V. Rafikov + * @version $Revision: 1.3 $ + */ +package org.apache.harmony.x.imageio.plugins.jpeg; + +import javax.imageio.spi.ImageWriterSpi; +import javax.imageio.ImageWriter; +import javax.imageio.ImageTypeSpecifier; +import java.io.IOException; +import java.util.Locale; + +public class JPEGImageWriterSpi extends ImageWriterSpi { + + public JPEGImageWriterSpi() { + super(JPEGSpiConsts.vendorName, JPEGSpiConsts.version, + JPEGSpiConsts.names, JPEGSpiConsts.suffixes, JPEGSpiConsts.MIMETypes, + JPEGSpiConsts.writerClassName, STANDARD_OUTPUT_TYPE, + JPEGSpiConsts.readerSpiNames, JPEGSpiConsts.supportsStandardStreamMetadataFormat /*TODO: support st. metadata format*/, + JPEGSpiConsts.nativeStreamMetadataFormatName, JPEGSpiConsts.nativeStreamMetadataFormatClassName, + JPEGSpiConsts.extraStreamMetadataFormatNames, JPEGSpiConsts.extraStreamMetadataFormatClassNames, + JPEGSpiConsts.supportsStandardImageMetadataFormat, JPEGSpiConsts.nativeImageMetadataFormatName, JPEGSpiConsts.nativeImageMetadataFormatClassName, + JPEGSpiConsts.extraImageMetadataFormatNames, JPEGSpiConsts.extraImageMetadataFormatClassNames); + } + + @Override + public boolean canEncodeImage(ImageTypeSpecifier imageTypeSpecifier) { + return true; + } + + @Override + public ImageWriter createWriterInstance(Object o) throws IOException { + return new JPEGImageWriter(this); + } + + @Override + public String getDescription(Locale locale) { + return "DRL JPEG Encoder"; + } +} diff --git a/awt/org/apache/harmony/x/imageio/plugins/jpeg/JPEGSpiConsts.java b/awt/org/apache/harmony/x/imageio/plugins/jpeg/JPEGSpiConsts.java new file mode 100644 index 0000000..c3b4a50 --- /dev/null +++ b/awt/org/apache/harmony/x/imageio/plugins/jpeg/JPEGSpiConsts.java @@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Rustem V. Rafikov + * @version $Revision: 1.2 $ + */ +package org.apache.harmony.x.imageio.plugins.jpeg; + +/** + * @author Rustem V. Rafikov + * @version $Revision: 1.2 $ + */ +public class JPEGSpiConsts { + private JPEGSpiConsts() {} + + public static final String vendorName = "Intel Corporation"; + public static final String version = "0.1 beta"; + + static final String readerClassName = "org.apache.harmony.x.imageio.plugins.jpeg.JPEGImageReader"; + static final String writerClassName = "org.apache.harmony.x.imageio.plugins.jpeg.JPEGImageWriter"; + + static final String[] names = {"jpeg", "jpg", "JPEG", "JPG"}; + static final String[] suffixes = {"jpeg", "jpg"}; + static final String[] MIMETypes = {"image/jpeg"}; + + static final String[] writerSpiNames = {"org.apache.harmony.x.imageio.plugins.jpeg.JPEGImageWriterSpi"}; + static final String[] readerSpiNames = {"org.apache.harmony.x.imageio.plugins.jpeg.JPEGImageReaderSpi"}; + + //-- TODO fill this stuff with correct data + static final boolean supportsStandardStreamMetadataFormat = false; + static final String nativeStreamMetadataFormatName = null; + static final String nativeStreamMetadataFormatClassName = null; + static final String[] extraStreamMetadataFormatNames = null; + static final String[] extraStreamMetadataFormatClassNames = null; + static final boolean supportsStandardImageMetadataFormat = false; + static final String nativeImageMetadataFormatName = + "org.apache.harmony.x.imageio.plugins.jpeg.MyFormatMetadata_1.0"; + static final String nativeImageMetadataFormatClassName = + "org.apache.harmony.x.imageio.plugins.jpeg.MyFormatMetadata"; + static final String[] extraImageMetadataFormatNames = null; + static final String[] extraImageMetadataFormatClassNames = null; + +} diff --git a/awt/org/apache/harmony/x/imageio/plugins/png/PNGImageReader.java b/awt/org/apache/harmony/x/imageio/plugins/png/PNGImageReader.java new file mode 100644 index 0000000..480041c --- /dev/null +++ b/awt/org/apache/harmony/x/imageio/plugins/png/PNGImageReader.java @@ -0,0 +1,106 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package org.apache.harmony.x.imageio.plugins.png; + +import org.apache.harmony.awt.gl.image.DecodingImageSource; +import org.apache.harmony.awt.gl.image.OffscreenImage; +import org.apache.harmony.x.imageio.plugins.jpeg.IISDecodingImageSource; + +import javax.imageio.ImageReader; +import javax.imageio.ImageTypeSpecifier; +import javax.imageio.ImageReadParam; +import javax.imageio.plugins.jpeg.JPEGImageReadParam; +import javax.imageio.spi.ImageReaderSpi; +import javax.imageio.stream.ImageInputStream; +import javax.imageio.metadata.IIOMetadata; +import java.io.IOException; +import java.util.Iterator; +import java.awt.image.BufferedImage; + +public class PNGImageReader extends ImageReader { + ImageInputStream iis; + + public PNGImageReader(ImageReaderSpi imageReaderSpi) { + super(imageReaderSpi); + } + + public int getNumImages(boolean allowSearch) throws IOException { + //-- TODO imlement + throw new UnsupportedOperationException("not implemented yet"); + } + + public int getWidth(int imageIndex) throws IOException { + //-- TODO imlement + throw new UnsupportedOperationException("not implemented yet"); + } + + public int getHeight(int imageIndex) throws IOException { + //-- TODO imlement + throw new UnsupportedOperationException("not implemented yet"); + } + + public Iterator getImageTypes(int imageIndex) throws IOException { + //-- TODO imlement + throw new UnsupportedOperationException("not implemented yet"); + } + + @Override + public IIOMetadata getStreamMetadata() throws IOException { + //-- TODO imlement + throw new UnsupportedOperationException("not implemented yet"); + } + + @Override + public IIOMetadata getImageMetadata(int imageIndex) throws IOException { + //-- TODO imlement + throw new UnsupportedOperationException("not implemented yet"); + } + + @Override + public BufferedImage read(int i, ImageReadParam imageReadParam) throws IOException { + if (iis == null) { + throw new IllegalArgumentException("input stream == null"); + } + + DecodingImageSource source = new IISDecodingImageSource(iis); + OffscreenImage image = new OffscreenImage(source); + source.addConsumer(image); + source.load(); + // The interrupted flag should be cleared because ImageDecoder interrupts + // current thread while decoding (due its architecture). + Thread.interrupted(); + return image.getBufferedImage(); + } + + @Override + public BufferedImage read(int i) throws IOException { + return read(i, null); + } + + @Override + public void setInput(Object input, boolean seekForwardOnly, boolean ignoreMetadata) { + super.setInput(input, seekForwardOnly, ignoreMetadata); + iis = (ImageInputStream) input; + } + + @Override + public ImageReadParam getDefaultReadParam() { + return new ImageReadParam(); + } +} diff --git a/awt/org/apache/harmony/x/imageio/plugins/png/PNGImageReaderSpi.java b/awt/org/apache/harmony/x/imageio/plugins/png/PNGImageReaderSpi.java new file mode 100644 index 0000000..50f8b10 --- /dev/null +++ b/awt/org/apache/harmony/x/imageio/plugins/png/PNGImageReaderSpi.java @@ -0,0 +1,88 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package org.apache.harmony.x.imageio.plugins.png; + +import org.apache.harmony.x.imageio.plugins.jpeg.JPEGSpiConsts; + +import javax.imageio.spi.ImageReaderSpi; +import javax.imageio.spi.ServiceRegistry; +import javax.imageio.ImageReader; +import javax.imageio.stream.ImageInputStream; +import java.io.IOException; +import java.util.Locale; + +public class PNGImageReaderSpi extends ImageReaderSpi { + static final String PNG_NAMES[] = new String[] {"png", "PNG"}; + static final String PNG_SUFFIXES[] = new String[] {"png"}; + static final String PNG_MIME_TYPES[] = new String[] {"image/png"}; + static final String PNG_READER_CLASS_NAME = "org.apache.harmony.x.imageio.plugins.png.PNGImageReader"; + static final String PNG_READER_SPI_NAMES[] = {"org.apache.harmony.x.imageio.plugins.png.PNGImageReaderSpi"}; + + public PNGImageReaderSpi() { + super( + JPEGSpiConsts.vendorName, JPEGSpiConsts.version, + PNG_NAMES, PNG_SUFFIXES, + PNG_MIME_TYPES, PNG_READER_CLASS_NAME, + STANDARD_INPUT_TYPE, null, + false, null, + null, null, + null, false, + null, null, + null, null + ); + } + + @Override + public boolean canDecodeInput(Object source) throws IOException { + ImageInputStream markable = (ImageInputStream) source; + markable.mark(); + + byte[] signature = new byte[8]; + markable.seek(0); + + int nBytes = markable.read(signature, 0, 8); + if(nBytes != 8) markable.read(signature, nBytes, 8-nBytes); + markable.reset(); + + // PNG signature: 137 80 78 71 13 10 26 10 + return (signature[0] & 0xFF) == 137 && + (signature[1] & 0xFF) == 80 && + (signature[2] & 0xFF) == 78 && + (signature[3] & 0xFF) == 71 && + (signature[4] & 0xFF) == 13 && + (signature[5] & 0xFF) == 10 && + (signature[6] & 0xFF) == 26 && + (signature[7] & 0xFF) == 10; + } + + @Override + public ImageReader createReaderInstance(Object extension) throws IOException { + return new PNGImageReader(this); + } + + @Override + public String getDescription(Locale locale) { + return "DRL PNG decoder"; + } + + @Override + public void onRegistration(ServiceRegistry registry, Class category) { + super.onRegistration(registry, category); + } +} diff --git a/awt/org/apache/harmony/x/imageio/plugins/png/PNGImageWriter.java b/awt/org/apache/harmony/x/imageio/plugins/png/PNGImageWriter.java new file mode 100644 index 0000000..e2a8d7d --- /dev/null +++ b/awt/org/apache/harmony/x/imageio/plugins/png/PNGImageWriter.java @@ -0,0 +1,247 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Viskov Nikolay + * @version $Revision$ + */ +package org.apache.harmony.x.imageio.plugins.png; + +import com.android.internal.awt.ImageOutputStreamWrapper; + +import java.awt.image.BufferedImage; +import java.awt.image.ColorModel; +import java.awt.image.DataBufferByte; +import java.awt.image.DataBufferInt; +import java.awt.image.IndexColorModel; +import java.awt.image.Raster; +import java.awt.image.RenderedImage; +import java.awt.image.SampleModel; +import java.awt.image.SinglePixelPackedSampleModel; +import java.awt.image.WritableRaster; +import java.io.IOException; + +import javax.imageio.IIOImage; +import javax.imageio.ImageTypeSpecifier; +import javax.imageio.ImageWriteParam; +import javax.imageio.ImageWriter; +import javax.imageio.metadata.IIOMetadata; +import javax.imageio.spi.ImageWriterSpi; +import javax.imageio.stream.ImageOutputStream; + +import org.apache.harmony.x.imageio.internal.nls.Messages; + +import org.apache.harmony.luni.util.NotImplementedException; + +import android.graphics.Bitmap; +import android.graphics.Bitmap.CompressFormat; + +public class PNGImageWriter extends ImageWriter { + + // /* ???AWT: Debugging + private static final boolean DEBUG = false; + private static Bitmap bm; + public static Bitmap getBitmap() { + return bm; + } + // */ + + private static int[][] BAND_OFFSETS = { + {}, { + 0 }, { + 0, 1 }, { + 0, 1, 2 }, { + 0, 1, 2, 3 } }; + + // Each pixel is a grayscale sample. + private static final int PNG_COLOR_TYPE_GRAY = 0; + // Each pixel is an R,G,B triple. + private static final int PNG_COLOR_TYPE_RGB = 2; + // Each pixel is a palette index, a PLTE chunk must appear. + private static final int PNG_COLOR_TYPE_PLTE = 3; + // Each pixel is a grayscale sample, followed by an alpha sample. + private static final int PNG_COLOR_TYPE_GRAY_ALPHA = 4; + // Each pixel is an R,G,B triple, followed by an alpha sample. + private static final int PNG_COLOR_TYPE_RGBA = 6; + + //???AWT: private static native void initIDs(Class iosClass); + + static { + //???AWT + /* + System.loadLibrary("pngencoder"); //$NON-NLS-1$ + initIDs(ImageOutputStream.class); + */ + } + + /* + private native int encode(byte[] input, int bytesInBuffer, int bytePixelSize, Object ios, int imageWidth, + int imageHeight, int bitDepth, int colorType, int[] palette, int i, boolean b); + */ + + protected PNGImageWriter(ImageWriterSpi iwSpi) { + super(iwSpi); + } + + @Override + public IIOMetadata convertStreamMetadata(IIOMetadata arg0, ImageWriteParam arg1) { + throw new NotImplementedException(); + } + + @Override + public IIOMetadata convertImageMetadata(IIOMetadata arg0, ImageTypeSpecifier arg1, ImageWriteParam arg2) { + throw new NotImplementedException(); + } + + @Override + public IIOMetadata getDefaultImageMetadata(ImageTypeSpecifier arg0, ImageWriteParam arg1) { + throw new NotImplementedException(); + } + + @Override + public IIOMetadata getDefaultStreamMetadata(ImageWriteParam arg0) { + throw new NotImplementedException(); + } + + @Override + public void write(IIOMetadata streamMetadata, IIOImage iioImage, ImageWriteParam param) throws IOException { + if (output == null) { + throw new IllegalStateException("Output not been set"); + } + if (iioImage == null) { + throw new IllegalArgumentException("Image equals null"); + } + // AWT???: I think this is not needed anymore + // if (iioImage.hasRaster() && !canWriteRasters()) { + // throw new UnsupportedOperationException("Can't write raster"); + //}// ImageOutputStreamImpl + + Raster sourceRaster; + RenderedImage img = null; + if (!iioImage.hasRaster()) { + img = iioImage.getRenderedImage(); + if (img instanceof BufferedImage) { + sourceRaster = ((BufferedImage) img).getRaster(); + } else { + sourceRaster = img.getData(); + } + } else { + sourceRaster = iioImage.getRaster(); + } + + SampleModel model = sourceRaster.getSampleModel(); + int srcWidth = sourceRaster.getWidth(); + int srcHeight = sourceRaster.getHeight(); + int numBands = model.getNumBands(); + + ColorModel colorModel = img.getColorModel(); + int pixelSize = colorModel.getPixelSize(); + int bytePixelSize = pixelSize / 8; + int bitDepth = pixelSize / numBands; + + // byte per band + int bpb = bitDepth > 8 ? 2 : 1; + + boolean isInterlace = true; + if (param instanceof PNGImageWriterParam) { + isInterlace = ((PNGImageWriterParam) param).getInterlace(); + } + + int colorType = PNG_COLOR_TYPE_GRAY; + int[] palette = null; + + if (colorModel instanceof IndexColorModel) { + if (bitDepth != 1 && bitDepth != 2 && bitDepth != 4 && bitDepth != 8) { +// Wrong bitDepth-numBands composition + throw new IllegalArgumentException(Messages.getString("imageio.1"));//$NON-NLS-1$ + } + if (numBands != 1) { +// Wrong bitDepth-numBands composition + throw new IllegalArgumentException(Messages.getString("imageio.1"));//$NON-NLS-1$ + } + + IndexColorModel icm = (IndexColorModel) colorModel; + palette = new int[icm.getMapSize()]; + icm.getRGBs(palette); + colorType = PNG_COLOR_TYPE_PLTE; + } + else if (numBands == 1) { + if (bitDepth != 1 && bitDepth != 2 && bitDepth != 4 && bitDepth != 8 && bitDepth != 16) { +// Wrong bitDepth-numBands composition + throw new IllegalArgumentException(Messages.getString("imageio.1"));//$NON-NLS-1$ + } + colorType = PNG_COLOR_TYPE_GRAY; + } + else if (numBands == 2) { + if (bitDepth != 8 && bitDepth != 16) { +// Wrong bitDepth-numBands composition + throw new IllegalArgumentException(Messages.getString("imageio.1"));//$NON-NLS-1$ + } + colorType = PNG_COLOR_TYPE_GRAY_ALPHA; + } + else if (numBands == 3) { + if (bitDepth != 8 && bitDepth != 16) { +// Wrong bitDepth-numBands composition + throw new IllegalArgumentException(Messages.getString("imageio.1")); //$NON-NLS-1$ + } + colorType = PNG_COLOR_TYPE_RGB; + } + else if (numBands == 4) { + if (bitDepth != 8 && bitDepth != 16) { + //Wrong bitDepth-numBands composition + throw new IllegalArgumentException(Messages.getString("imageio.1")); //$NON-NLS-1$ + } + colorType = PNG_COLOR_TYPE_RGBA; + } + + /* ???AWT: I think this is not needed anymore + int dbufferLenght = bytePixelSize * imageHeight * imageWidth; + DataBufferByte dbuffer = new DataBufferByte(dbufferLenght); + + WritableRaster scanRaster = Raster.createInterleavedRaster(dbuffer, imageWidth, imageHeight, bpb * numBands + * imageWidth, bpb * numBands, BAND_OFFSETS[numBands], null); + + scanRaster.setRect(((BufferedImage) image).getRaster()// image.getData() + .createChild(0, 0, imageWidth, imageHeight, 0, 0, null)); + */ + + if (DEBUG) { + System.out.println("**** raster:" + sourceRaster); + System.out.println("**** model:" + model); + System.out.println("**** type:" + colorType); + } + + if (model instanceof SinglePixelPackedSampleModel) { + DataBufferInt ibuf = (DataBufferInt)sourceRaster.getDataBuffer(); + int[] pixels = ibuf.getData(); + + // Create a bitmap with the pixel + bm = Bitmap.createBitmap(pixels, srcWidth, srcHeight, Bitmap.Config.ARGB_8888); + + // Use Bitmap.compress() to write the image + ImageOutputStream ios = (ImageOutputStream) getOutput(); + ImageOutputStreamWrapper iosw = new ImageOutputStreamWrapper(ios); + bm.compress(CompressFormat.PNG, 100, iosw); + } else { + // ???AWT: Add support for other color models + throw new RuntimeException("Color model not supported yet"); + } + } + + public ImageWriteParam getDefaultWriteParam() { + return new PNGImageWriterParam(); + } +} diff --git a/awt/org/apache/harmony/x/imageio/plugins/png/PNGImageWriterParam.java b/awt/org/apache/harmony/x/imageio/plugins/png/PNGImageWriterParam.java new file mode 100644 index 0000000..bf3a000 --- /dev/null +++ b/awt/org/apache/harmony/x/imageio/plugins/png/PNGImageWriterParam.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Viskov Nikolay + * @version $Revision$ + */ +package org.apache.harmony.x.imageio.plugins.png; + +import javax.imageio.ImageWriteParam; + +public class PNGImageWriterParam extends ImageWriteParam { + + private boolean isInterlace = true; + + public PNGImageWriterParam() { + super(); + } + + public boolean getInterlace() { + return isInterlace; + } + + public void setInterlace(boolean b) { + isInterlace = b; + } + +} diff --git a/awt/org/apache/harmony/x/imageio/plugins/png/PNGImageWriterSpi.java b/awt/org/apache/harmony/x/imageio/plugins/png/PNGImageWriterSpi.java new file mode 100644 index 0000000..6eed14d --- /dev/null +++ b/awt/org/apache/harmony/x/imageio/plugins/png/PNGImageWriterSpi.java @@ -0,0 +1,113 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Viskov Nikolay + * @version $Revision$ + */ +package org.apache.harmony.x.imageio.plugins.png; + +import java.awt.image.ColorModel; +import java.awt.image.DataBufferByte; +import java.awt.image.IndexColorModel; +import java.io.IOException; +import java.util.Locale; + +import javax.imageio.ImageTypeSpecifier; +import javax.imageio.ImageWriter; +import javax.imageio.spi.ImageWriterSpi; + +public class PNGImageWriterSpi extends ImageWriterSpi { + + public PNGImageWriterSpi() { + super("Intel Corporation",// vendorName + "1.0",// version + new String[] { + "png", "PNG" },// names + new String[] { + "png", "PNG" },// suffixes + new String[] { + "image/png" },// MIMETypes + "org.apache.harmony.x.imageio.plugins.png.PNGImageWriter",// writerClassName + STANDARD_OUTPUT_TYPE,// outputTypes + new String[] { + "org.apache.harmony.x.imageio.plugins.png.PNGImageWriterSpi" },// readerSpiNames + false,// supportsStandardStreamMetadataFormat + null,// nativeStreamMetadataFormatName + null,// nativeStreamMetadataFormatClassName + null,// extraStreamMetadataFormatNames + null,// extraStreamMetadataFormatClassNames + false,// supportsStandardImageMetadataFormat + null,// nativeImageMetadataFormatName + null,// nativeImageMetadataFormatClassName + null,// extraImageMetadataFormatNames + null// extraImageMetadataFormatClassNames + ); + } + + @Override + public boolean canEncodeImage(ImageTypeSpecifier type) { + boolean canEncode = true; + + int numBands = type.getSampleModel().getNumBands(); + + ColorModel colorModel = type.getColorModel(); + + int bitDepth = colorModel.getPixelSize() / numBands; + + if (colorModel instanceof IndexColorModel) { + if (bitDepth != 1 && bitDepth != 2 && bitDepth != 4 && bitDepth != 8) { + canEncode = false; + } + if (numBands != 1) { + canEncode = false; + } + } + else if (numBands == 1) { + if (bitDepth != 1 && bitDepth != 2 && bitDepth != 4 && bitDepth != 8 && bitDepth != 16) { + canEncode = false; + } + } + else if (numBands == 2) { + if (bitDepth != 8 && bitDepth != 16) { + canEncode = false; + } + } + else if (numBands == 3) { + if (bitDepth != 8 && bitDepth != 16) { + canEncode = false; + } + } + else if (numBands == 4) { + if (bitDepth != 8 && bitDepth != 16) { + canEncode = false; + } + } + + return canEncode; + } + + @Override + public ImageWriter createWriterInstance(Object arg0) throws IOException { + return new PNGImageWriter(this); + } + + @Override + public String getDescription(Locale arg0) { + return "DRL PNG encoder"; + } + +} diff --git a/awt/org/apache/harmony/x/imageio/spi/FileIISSpi.java b/awt/org/apache/harmony/x/imageio/spi/FileIISSpi.java new file mode 100644 index 0000000..d4fdd76 --- /dev/null +++ b/awt/org/apache/harmony/x/imageio/spi/FileIISSpi.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Rustem V. Rafikov + * @version $Revision: 1.2 $ + */ +package org.apache.harmony.x.imageio.spi; + +import javax.imageio.spi.ImageInputStreamSpi; +import javax.imageio.stream.ImageInputStream; +import javax.imageio.stream.FileImageOutputStream; +import javax.imageio.stream.FileImageInputStream; +import java.io.File; +import java.io.IOException; +import java.util.Locale; + +public class FileIISSpi extends ImageInputStreamSpi { + private static final String vendor = "Apache"; + + private static final String ver = "0.1"; + + public FileIISSpi() { + super(vendor, ver, File.class); + } + + @Override + public ImageInputStream createInputStreamInstance(Object input, boolean useCache, + File cacheDir) throws IOException { + if (File.class.isInstance(input)) { + return new FileImageInputStream((File) input); + } + throw new IllegalArgumentException("input is not an instance of java.io.File"); + } + + @Override + public String getDescription(Locale locale) { + return "File IIS Spi"; + } +} diff --git a/awt/org/apache/harmony/x/imageio/spi/FileIOSSpi.java b/awt/org/apache/harmony/x/imageio/spi/FileIOSSpi.java new file mode 100644 index 0000000..acda6a1 --- /dev/null +++ b/awt/org/apache/harmony/x/imageio/spi/FileIOSSpi.java @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Rustem V. Rafikov + * @version $Revision: 1.2 $ + */ +package org.apache.harmony.x.imageio.spi; + +import javax.imageio.spi.ImageOutputStreamSpi; +import javax.imageio.stream.ImageOutputStream; +import javax.imageio.stream.FileImageOutputStream; +import java.io.File; +import java.io.IOException; +import java.util.Locale; + +public class FileIOSSpi extends ImageOutputStreamSpi { + private static final String vendor = "Apache"; + + private static final String ver = "0.1"; + + public FileIOSSpi() { + super(vendor, ver, File.class); + } + + @Override + public ImageOutputStream createOutputStreamInstance(Object output, boolean useCache, + File cacheDir) throws IOException { + if (output instanceof File) { + return new FileImageOutputStream((File) output); + } + throw new IllegalArgumentException("output is not instance of File"); + } + + @Override + public String getDescription(Locale locale) { + return "File IOS Spi"; + } +} diff --git a/awt/org/apache/harmony/x/imageio/spi/InputStreamIISSpi.java b/awt/org/apache/harmony/x/imageio/spi/InputStreamIISSpi.java new file mode 100644 index 0000000..ed2fef0 --- /dev/null +++ b/awt/org/apache/harmony/x/imageio/spi/InputStreamIISSpi.java @@ -0,0 +1,59 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package org.apache.harmony.x.imageio.spi; + +import javax.imageio.spi.ImageInputStreamSpi; +import javax.imageio.stream.*; +import java.io.OutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.Locale; + +public class InputStreamIISSpi extends ImageInputStreamSpi { + private static final String vendor = "Apache"; + + private static final String ver = "0.1"; + + public InputStreamIISSpi() { + super(vendor, ver, InputStream.class); + } + + @Override + public String getDescription(Locale locale) { + return "Output Stream IOS Spi"; + } + + @Override + public boolean canUseCacheFile() { + return true; + } + + @Override + public ImageInputStream createInputStreamInstance(Object input, boolean useCache, File cacheDir) throws IOException { + if (input instanceof InputStream) { + if (useCache) { + return new FileCacheImageInputStream((InputStream) input, cacheDir); + } else { + return new MemoryCacheImageInputStream((InputStream) input); + } + } + throw new IllegalArgumentException("Output is not an instance of InputStream"); + } +} diff --git a/awt/org/apache/harmony/x/imageio/spi/OutputStreamIOSSpi.java b/awt/org/apache/harmony/x/imageio/spi/OutputStreamIOSSpi.java new file mode 100644 index 0000000..dd1e88d --- /dev/null +++ b/awt/org/apache/harmony/x/imageio/spi/OutputStreamIOSSpi.java @@ -0,0 +1,60 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package org.apache.harmony.x.imageio.spi; + +import javax.imageio.spi.ImageOutputStreamSpi; +import javax.imageio.stream.ImageOutputStream; +import javax.imageio.stream.FileCacheImageOutputStream; +import javax.imageio.stream.MemoryCacheImageOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Locale; + +public class OutputStreamIOSSpi extends ImageOutputStreamSpi { + private static final String vendor = "Apache"; + + private static final String ver = "0.1"; + + public OutputStreamIOSSpi() { + super(vendor, ver, OutputStream.class); + } + + @Override + public ImageOutputStream createOutputStreamInstance(Object output, boolean useCache, File cacheDir) throws IOException { + if (output instanceof OutputStream) { + if (useCache) { + return new FileCacheImageOutputStream((OutputStream) output, cacheDir); + } else { + return new MemoryCacheImageOutputStream((OutputStream) output); + } + } + throw new IllegalArgumentException("Output is not an instance of OutputStream"); + } + + @Override + public String getDescription(Locale locale) { + return "Output Stream IOS Spi"; + } + + @Override + public boolean canUseCacheFile() { + return true; + } +} diff --git a/awt/org/apache/harmony/x/imageio/spi/RAFIISSpi.java b/awt/org/apache/harmony/x/imageio/spi/RAFIISSpi.java new file mode 100644 index 0000000..f97eb87 --- /dev/null +++ b/awt/org/apache/harmony/x/imageio/spi/RAFIISSpi.java @@ -0,0 +1,54 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Rustem V. Rafikov + * @version $Revision: 1.2 $ + */ +package org.apache.harmony.x.imageio.spi; + +import javax.imageio.spi.ImageInputStreamSpi; +import javax.imageio.stream.ImageInputStream; +import javax.imageio.stream.FileImageInputStream; +import java.io.File; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.util.Locale; + +public class RAFIISSpi extends ImageInputStreamSpi { + private static final String vendor = "Apache"; + + private static final String ver = "0.1"; + + public RAFIISSpi() { + super(vendor, ver, RandomAccessFile.class); + } + + @Override + public ImageInputStream createInputStreamInstance(Object input, boolean useCache, + File cacheDir) throws IOException { + if (RandomAccessFile.class.isInstance(input)) { + return new FileImageInputStream((RandomAccessFile) input); + } + throw new IllegalArgumentException( + "input is not an instance of java.io.RandomAccessFile"); + } + + @Override + public String getDescription(Locale locale) { + return "RandomAccessFile IIS Spi"; + } +} diff --git a/awt/org/apache/harmony/x/imageio/spi/RAFIOSSpi.java b/awt/org/apache/harmony/x/imageio/spi/RAFIOSSpi.java new file mode 100644 index 0000000..a9d3649 --- /dev/null +++ b/awt/org/apache/harmony/x/imageio/spi/RAFIOSSpi.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Rustem V. Rafikov + * @version $Revision: 1.2 $ + */ +package org.apache.harmony.x.imageio.spi; + +import javax.imageio.spi.ImageOutputStreamSpi; +import javax.imageio.stream.ImageOutputStream; +import javax.imageio.stream.FileImageOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.util.Locale; + +public class RAFIOSSpi extends ImageOutputStreamSpi { + private static final String vendor = "Apache"; + + private static final String ver = "0.1"; + + public RAFIOSSpi() { + super(vendor, ver, RandomAccessFile.class); + } + + @Override + public ImageOutputStream createOutputStreamInstance(Object output, boolean useCache, + File cacheDir) throws IOException { + if (output instanceof RandomAccessFile) { + return new FileImageOutputStream((RandomAccessFile) output); + } + throw new IllegalArgumentException("output is not instance of java.io.RandomAccessFile"); + } + + @Override + public String getDescription(Locale locale) { + return "RandomAccessFile IOS Spi"; + } +} diff --git a/awt/org/apache/harmony/x/imageio/stream/RandomAccessMemoryCache.java b/awt/org/apache/harmony/x/imageio/stream/RandomAccessMemoryCache.java new file mode 100644 index 0000000..64f7b2a --- /dev/null +++ b/awt/org/apache/harmony/x/imageio/stream/RandomAccessMemoryCache.java @@ -0,0 +1,226 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package org.apache.harmony.x.imageio.stream; + +import java.util.ArrayList; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +public final class RandomAccessMemoryCache { + private static final int BLOCK_SHIFT = 9; + private static final int BLOCK_SIZE = 1 << BLOCK_SHIFT; + private static final int BLOCK_MASK = BLOCK_SIZE - 1; + + private long length; + + private int firstUndisposed = 0; + + private ArrayList blocks = new ArrayList(); + + public RandomAccessMemoryCache() { + } + + public long length() { + return length; + } + + public void close() { + blocks.clear(); + length = 0; + } + + private void grow(long pos) { + int blocksNeeded = (int)(pos >> BLOCK_SHIFT) - blocks.size() + 1; + for (int i=0; i < blocksNeeded; i++) { + blocks.add(new byte[BLOCK_SIZE]); + } + + length = pos + 1; + } + + public void putData(int oneByte, long pos) { + if (pos >= length) { + grow(pos); + } + + byte[] block = blocks.get((int)(pos >> BLOCK_SHIFT)); + block[(int)(pos & BLOCK_MASK)] = (byte) oneByte; + } + + public void putData(byte[] buffer, int offset, int count, long pos) { + if (count > buffer.length - offset || count < 0 || offset < 0) { + throw new IndexOutOfBoundsException(); + } + if (count == 0){ + return; + } + + long lastPos = pos + count - 1; + if (lastPos >= length) { + grow(lastPos); + } + + while (count > 0) { + byte[] block = blocks.get((int)(pos >> BLOCK_SHIFT)); + int blockOffset = (int)(pos & BLOCK_MASK); + int toCopy = Math.min(BLOCK_SIZE - blockOffset, count); + System.arraycopy(buffer, offset, block, blockOffset, toCopy); + pos += toCopy; + count -= toCopy; + offset += toCopy; + } + } + + public int getData(long pos) { + if (pos >= length) { + return -1; + } + + byte[] block = blocks.get((int)(pos >> BLOCK_SHIFT)); + return block[(int)(pos & BLOCK_MASK)] & 0xFF; + } + + public int getData(byte[] buffer, int offset, int count, long pos) { + if (count > buffer.length - offset || count < 0 || offset < 0) { + throw new IndexOutOfBoundsException(); + } + if (count == 0) { + return 0; + } + if (pos >= length) { + return -1; + } + + if (count + pos > length) { + count = (int) (length - pos); + } + + byte[] block = blocks.get((int)(pos >> BLOCK_SHIFT)); + int nbytes = Math.min(count, BLOCK_SIZE - (int)(pos & BLOCK_MASK)); + System.arraycopy(block, (int)(pos & BLOCK_MASK), buffer, offset, nbytes); + + return nbytes; + } + /* + public void seek(long pos) throws IOException { + if (pos < 0) { + throw new IOException("seek position is negative"); + } + this.pos = pos; + } + + public void readFully(byte[] buffer) throws IOException { + readFully(buffer, 0, buffer.length); + } + + public void readFully(byte[] buffer, int offset, int count) throws IOException { + if (0 <= offset && offset <= buffer.length && 0 <= count && count <= buffer.length - offset) { + while (count > 0) { + int result = read(buffer, offset, count); + if (result >= 0) { + offset += result; + count -= result; + } else { + throw new EOFException(); + } + } + } else { + throw new IndexOutOfBoundsException(); + } + } + + public long getFilePointer() { + return pos; + } +*/ + + public void freeBefore(long pos) { + int blockIdx = (int)(pos >> BLOCK_SHIFT); + if (blockIdx <= firstUndisposed) { // Nothing to do + return; + } + + for (int i = firstUndisposed; i < blockIdx; i++) { + blocks.set(i, null); + } + + firstUndisposed = blockIdx; + } + + public int appendData(InputStream is, int count) throws IOException { + if (count <= 0) { + return 0; + } + + long startPos = length; + long lastPos = length + count - 1; + grow(lastPos); // Changes length + + int blockIdx = (int)(startPos >> BLOCK_SHIFT); + int offset = (int) (startPos & BLOCK_MASK); + + int bytesAppended = 0; + + while (count > 0) { + byte[] block = blocks.get(blockIdx); + int toCopy = Math.min(BLOCK_SIZE - offset, count); + count -= toCopy; + + while (toCopy > 0) { + int bytesRead = is.read(block, offset, toCopy); + + if (bytesRead < 0) { + length -= (count - bytesAppended); + return bytesAppended; + } + + toCopy -= bytesRead; + offset += bytesRead; + } + + blockIdx++; + offset = 0; + } + + return count; + } + + public void getData(OutputStream os, int count, long pos) throws IOException { + if (pos + count > length) { + throw new IndexOutOfBoundsException("Argument out of cache"); + } + + int blockIdx = (int)(pos >> BLOCK_SHIFT); + int offset = (int) (pos & BLOCK_MASK); + if (blockIdx < firstUndisposed) { + throw new IndexOutOfBoundsException("The requested data are already disposed"); + } + + while (count > 0) { + byte[] block = blocks.get(blockIdx); + int toWrite = Math.min(BLOCK_SIZE - offset, count); + os.write(block, offset, toWrite); + + blockIdx++; + offset = 0; + count -= toWrite; + } + } +} -- cgit v1.1