| /* ==================================================================== |
| 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.poi.hwmf.record; |
| |
| import java.awt.Shape; |
| import java.awt.geom.Arc2D; |
| import java.awt.geom.Area; |
| import java.awt.geom.Dimension2D; |
| import java.awt.geom.Ellipse2D; |
| import java.awt.geom.Line2D; |
| import java.awt.geom.Path2D; |
| import java.awt.geom.Point2D; |
| import java.awt.geom.Rectangle2D; |
| import java.awt.geom.RoundRectangle2D; |
| import java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.function.Supplier; |
| |
| import org.apache.poi.hwmf.draw.HwmfGraphics; |
| import org.apache.poi.hwmf.draw.HwmfGraphics.FillDrawStyle; |
| import org.apache.poi.util.Dimension2DDouble; |
| import org.apache.poi.util.GenericRecordJsonWriter; |
| import org.apache.poi.util.GenericRecordUtil; |
| import org.apache.poi.util.Internal; |
| import org.apache.poi.util.LittleEndianConsts; |
| import org.apache.poi.util.LittleEndianInputStream; |
| |
| @SuppressWarnings("WeakerAccess") |
| public final class HwmfDraw { |
| |
| private HwmfDraw() {} |
| |
| /** |
| * The META_MOVETO record sets the output position in the playback device context to a specified |
| * point. |
| */ |
| public static class WmfMoveTo implements HwmfRecord { |
| |
| protected final Point2D point = new Point2D.Double(); |
| |
| @Override |
| public HwmfRecordType getWmfRecordType() { |
| return HwmfRecordType.moveTo; |
| } |
| |
| @Override |
| public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { |
| return readPointS(leis, point); |
| } |
| |
| @Override |
| public void draw(HwmfGraphics ctx) { |
| ctx.getProperties().setLocation(point); |
| } |
| |
| @Override |
| public String toString() { |
| return GenericRecordJsonWriter.marshal(this); |
| } |
| |
| public Point2D getPoint() { |
| return point; |
| } |
| |
| @Override |
| public Map<String, Supplier<?>> getGenericProperties() { |
| return GenericRecordUtil.getGenericProperties("point", this::getPoint); |
| } |
| } |
| |
| /** |
| * The META_LINETO record draws a line from the drawing position that is defined in the playback |
| * device context up to, but not including, the specified point. |
| */ |
| public static class WmfLineTo implements HwmfRecord { |
| |
| protected final Point2D point = new Point2D.Double(); |
| |
| @Override |
| public HwmfRecordType getWmfRecordType() { |
| return HwmfRecordType.lineTo; |
| } |
| |
| @Override |
| public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { |
| return readPointS(leis, point); |
| } |
| |
| @Override |
| public void draw(HwmfGraphics ctx) { |
| Point2D start = ctx.getProperties().getLocation(); |
| Line2D line = new Line2D.Double(start, point); |
| ctx.draw(line); |
| ctx.getProperties().setLocation(point); |
| } |
| |
| @Override |
| public String toString() { |
| return GenericRecordJsonWriter.marshal(this); |
| } |
| |
| public Point2D getPoint() { |
| return point; |
| } |
| |
| @Override |
| public Map<String, Supplier<?>> getGenericProperties() { |
| return GenericRecordUtil.getGenericProperties("point", this::getPoint); |
| } |
| } |
| |
| /** |
| * The META_POLYGON record paints a polygon consisting of two or more vertices connected by |
| * straight lines. The polygon is outlined by using the pen and filled by using the brush and polygon fill |
| * mode that are defined in the playback device context. |
| */ |
| public static class WmfPolygon implements HwmfRecord { |
| |
| protected Path2D poly; |
| |
| @Override |
| public HwmfRecordType getWmfRecordType() { |
| return HwmfRecordType.polygon; |
| } |
| |
| @Override |
| public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { |
| //A 16-bit signed integer that defines the number of points in the array. |
| int numberOfPoints = leis.readShort(); |
| |
| poly = new Path2D.Double(Path2D.WIND_EVEN_ODD, numberOfPoints); |
| for (int i=0; i<numberOfPoints; i++) { |
| // A 16-bit signed integer that defines the horizontal (x) coordinate of the point. |
| int x = leis.readShort(); |
| // A 16-bit signed integer that defines the vertical (y) coordinate of the point. |
| int y = leis.readShort(); |
| if (i==0) { |
| poly.moveTo(x, y); |
| } else { |
| poly.lineTo(x, y); |
| } |
| } |
| |
| if (numberOfPoints > 0 && addClose()) { |
| // polygons are closed / polylines not |
| poly.closePath(); |
| } |
| |
| return LittleEndianConsts.SHORT_SIZE+numberOfPoints*LittleEndianConsts.INT_SIZE; |
| } |
| |
| @Override |
| public void draw(HwmfGraphics ctx) { |
| Path2D p = (Path2D)poly.clone(); |
| // don't close the path |
| p.setWindingRule(ctx.getProperties().getWindingRule()); |
| getFillDrawStyle().handler.accept(ctx, p); |
| } |
| |
| @Override |
| public String toString() { |
| return GenericRecordJsonWriter.marshal(this); |
| } |
| |
| /** |
| * @return true, if the shape should be filled |
| */ |
| protected FillDrawStyle getFillDrawStyle() { |
| return FillDrawStyle.FILL; |
| } |
| |
| public Path2D getPoly() { |
| return poly; |
| } |
| |
| /** |
| * @return {@code true} if the path needs to be closed |
| */ |
| protected boolean addClose() { |
| return true; |
| } |
| |
| @Override |
| public Map<String, Supplier<?>> getGenericProperties() { |
| return GenericRecordUtil.getGenericProperties("poly", this::getPoly); |
| } |
| } |
| |
| /** |
| * The META_POLYLINE record draws a series of line segments by connecting the points in the |
| * specified array. |
| */ |
| public static class WmfPolyline extends WmfPolygon { |
| |
| @Override |
| public HwmfRecordType getWmfRecordType() { |
| return HwmfRecordType.polyline; |
| } |
| |
| @Override |
| protected FillDrawStyle getFillDrawStyle() { |
| return FillDrawStyle.DRAW; |
| } |
| |
| @Override |
| protected boolean addClose() { |
| return false; |
| } |
| } |
| |
| /** |
| * The META_ELLIPSE record draws an ellipse. The center of the ellipse is the center of the specified |
| * bounding rectangle. The ellipse is outlined by using the pen and is filled by using the brush; these |
| * are defined in the playback device context. |
| */ |
| public static class WmfEllipse implements HwmfRecord { |
| protected final Rectangle2D bounds = new Rectangle2D.Double(); |
| |
| @Override |
| public HwmfRecordType getWmfRecordType() { |
| return HwmfRecordType.ellipse; |
| } |
| |
| @Override |
| public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { |
| return readBounds(leis, bounds); |
| } |
| |
| @Override |
| public void draw(HwmfGraphics ctx) { |
| ctx.fill(getShape()); |
| } |
| |
| protected Ellipse2D getShape() { |
| return new Ellipse2D.Double(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight()); |
| } |
| |
| @Override |
| public String toString() { |
| return GenericRecordJsonWriter.marshal(this); |
| } |
| |
| public Rectangle2D getBounds() { |
| return bounds; |
| } |
| |
| @Override |
| public Map<String, Supplier<?>> getGenericProperties() { |
| return GenericRecordUtil.getGenericProperties("bounds", this::getBounds); |
| } |
| } |
| |
| |
| /** |
| * The META_FRAMEREGION record draws a border around a specified region using a specified brush. |
| */ |
| public static class WmfFrameRegion implements HwmfRecord { |
| /** |
| * A 16-bit unsigned integer used to index into the WMF Object Table to get |
| * the region to be framed. |
| */ |
| protected int regionIndex; |
| /** |
| * A 16-bit unsigned integer used to index into the WMF Object Table to get the |
| * Brush to use for filling the region. |
| */ |
| protected int brushIndex; |
| |
| /** |
| * The region frame, in logical units |
| */ |
| protected final Dimension2D frame = new Dimension2DDouble(); |
| |
| @Override |
| public HwmfRecordType getWmfRecordType() { |
| return HwmfRecordType.frameRegion; |
| } |
| |
| @Override |
| public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { |
| regionIndex = leis.readUShort(); |
| brushIndex = leis.readUShort(); |
| // A 16-bit signed integer that defines the height/width, in logical units, of the region frame. |
| int height = leis.readShort(); |
| int width = leis.readShort(); |
| frame.setSize(width, height); |
| return 4*LittleEndianConsts.SHORT_SIZE; |
| } |
| |
| @Override |
| public void draw(HwmfGraphics ctx) { |
| ctx.applyObjectTableEntry(brushIndex); |
| ctx.applyObjectTableEntry(regionIndex); |
| Rectangle2D inner = ctx.getProperties().getRegion().getBounds(); |
| double x = inner.getX()-frame.getWidth(); |
| double y = inner.getY()-frame.getHeight(); |
| double w = inner.getWidth()+2.0*frame.getWidth(); |
| double h = inner.getHeight()+2.0*frame.getHeight(); |
| Rectangle2D outer = new Rectangle2D.Double(x,y,w,h); |
| Area frame = new Area(outer); |
| frame.subtract(new Area(inner)); |
| ctx.fill(frame); |
| } |
| |
| public int getRegionIndex() { |
| return regionIndex; |
| } |
| |
| public int getBrushIndex() { |
| return brushIndex; |
| } |
| |
| public Dimension2D getFrame() { |
| return frame; |
| } |
| |
| @Override |
| public Map<String, Supplier<?>> getGenericProperties() { |
| return GenericRecordUtil.getGenericProperties( |
| "regionIndex", this::getRegionIndex, |
| "brushIndex", this::getBrushIndex, |
| "frame", this::getFrame); |
| } |
| } |
| |
| /** |
| * The META_POLYPOLYGON record paints a series of closed polygons. Each polygon is outlined by |
| * using the pen and filled by using the brush and polygon fill mode; these are defined in the playback |
| * device context. The polygons drawn by this function can overlap. |
| */ |
| public static class WmfPolyPolygon implements HwmfRecord { |
| |
| protected final List<Path2D> polyList = new ArrayList<>(); |
| |
| @Override |
| public HwmfRecordType getWmfRecordType() { |
| return HwmfRecordType.polyPolygon; |
| } |
| |
| @Override |
| public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { |
| // see also CVE-2008-3014 - https://dl.packetstormsecurity.net/papers/attack/CVE-2008-3014.pdf ;) |
| // A 16-bit unsigned integer that defines the number of polygons in the object. |
| int numberOfPolygons = leis.readUShort(); |
| // A NumberOfPolygons array of 16-bit unsigned integers that define the number of points for |
| // each polygon in the object. |
| int[] pointsPerPolygon = new int[numberOfPolygons]; |
| |
| int size = LittleEndianConsts.SHORT_SIZE; |
| |
| for (int i=0; i<numberOfPolygons; i++) { |
| pointsPerPolygon[i] = leis.readUShort(); |
| size += LittleEndianConsts.SHORT_SIZE; |
| } |
| |
| for (int nPoints : pointsPerPolygon) { |
| // An array of 16-bit signed integers that define the coordinates of the polygons. |
| // (Note: MS-WMF wrongly says unsigned integers ...) |
| Path2D poly = new Path2D.Double(Path2D.WIND_EVEN_ODD, nPoints); |
| for (int i=0; i<nPoints; i++) { |
| int x = leis.readShort(); |
| int y = leis.readShort(); |
| size += 2*LittleEndianConsts.SHORT_SIZE; |
| if (i == 0) { |
| poly.moveTo(x, y); |
| } else { |
| poly.lineTo(x, y); |
| } |
| } |
| poly.closePath(); |
| polyList.add(poly); |
| } |
| |
| return size; |
| } |
| |
| @Override |
| public void draw(HwmfGraphics ctx) { |
| Shape shape = getShape(ctx); |
| if (shape == null) { |
| return; |
| } |
| |
| switch (getFillDrawStyle()) { |
| case DRAW: |
| ctx.draw(shape); |
| break; |
| case FILL: |
| ctx.fill(shape); |
| break; |
| case FILL_DRAW: |
| ctx.fill(shape); |
| ctx.draw(shape); |
| break; |
| } |
| } |
| |
| protected FillDrawStyle getFillDrawStyle() { |
| // Each polygon SHOULD be outlined using the current pen, and filled using the current brush and |
| // polygon fill mode that are defined in the playback device context. The polygons defined by this |
| // record can overlap. |
| return FillDrawStyle.FILL_DRAW; |
| } |
| |
| /** |
| * @return true, if a polyline should be closed, i.e. is a polygon |
| */ |
| protected boolean isClosed() { |
| return true; |
| } |
| |
| protected Shape getShape(HwmfGraphics ctx) { |
| int windingRule = ctx.getProperties().getWindingRule(); |
| |
| if (isClosed()) { |
| Area area = null; |
| for (Path2D poly : polyList) { |
| Path2D p = (Path2D)poly.clone(); |
| p.setWindingRule(windingRule); |
| Area newArea = new Area(p); |
| if (area == null) { |
| area = newArea; |
| } else { |
| area.exclusiveOr(newArea); |
| } |
| } |
| return area; |
| } else { |
| Path2D path = new Path2D.Double(); |
| path.setWindingRule(windingRule); |
| for (Path2D poly : polyList) { |
| path.append(poly, false); |
| } |
| return path; |
| } |
| } |
| |
| @Override |
| public String toString() { |
| return GenericRecordJsonWriter.marshal(this); |
| } |
| |
| public List<Path2D> getPolyList() { |
| return polyList; |
| } |
| |
| @Override |
| public Map<String, Supplier<?>> getGenericProperties() { |
| return GenericRecordUtil.getGenericProperties("polyList", this::getPolyList); |
| } |
| } |
| |
| /** |
| * The META_RECTANGLE record paints a rectangle. The rectangle is outlined by using the pen and |
| * filled by using the brush that are defined in the playback device context. |
| */ |
| public static class WmfRectangle implements HwmfRecord { |
| protected final Rectangle2D bounds = new Rectangle2D.Double(); |
| |
| @Override |
| public HwmfRecordType getWmfRecordType() { |
| return HwmfRecordType.rectangle; |
| } |
| |
| @Override |
| public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { |
| return readBounds(leis, bounds); |
| } |
| |
| @Override |
| public void draw(HwmfGraphics ctx) { |
| ctx.fill(bounds); |
| } |
| |
| @Override |
| public String toString() { |
| return GenericRecordJsonWriter.marshal(this); |
| } |
| |
| public Rectangle2D getBounds() { |
| return bounds; |
| } |
| |
| @Override |
| public Map<String, Supplier<?>> getGenericProperties() { |
| return GenericRecordUtil.getGenericProperties("bounds", this::getBounds); |
| } |
| } |
| |
| /** |
| * The META_SETPIXEL record sets the pixel at the specified coordinates to the specified color. |
| */ |
| public static class WmfSetPixel implements HwmfRecord { |
| /** |
| * A ColorRef Object that defines the color value. |
| */ |
| protected final HwmfColorRef colorRef = new HwmfColorRef(); |
| |
| protected final Point2D point = new Point2D.Double(); |
| |
| |
| @Override |
| public HwmfRecordType getWmfRecordType() { |
| return HwmfRecordType.setPixel; |
| } |
| |
| @Override |
| public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { |
| int size = colorRef.init(leis); |
| return size+ readPointS(leis, point); |
| } |
| |
| @Override |
| public void draw(HwmfGraphics ctx) { |
| Shape s = new Rectangle2D.Double(point.getX(), point.getY(), 1, 1); |
| ctx.fill(s); |
| } |
| |
| public HwmfColorRef getColorRef() { |
| return colorRef; |
| } |
| |
| public Point2D getPoint() { |
| return point; |
| } |
| |
| @Override |
| public Map<String, Supplier<?>> getGenericProperties() { |
| return GenericRecordUtil.getGenericProperties( |
| "colorRef", this::getColorRef, |
| "point", this::getPoint |
| ); |
| } |
| } |
| |
| /** |
| * The META_ROUNDRECT record paints a rectangle with rounded corners. The rectangle is outlined |
| * using the pen and filled using the brush, as defined in the playback device context. |
| */ |
| public static class WmfRoundRect implements HwmfRecord { |
| protected final Dimension2D corners = new Dimension2DDouble(); |
| |
| protected final Rectangle2D bounds = new Rectangle2D.Double(); |
| |
| |
| @Override |
| public HwmfRecordType getWmfRecordType() { |
| return HwmfRecordType.roundRect; |
| } |
| |
| @Override |
| public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { |
| // A 16-bit signed integer that defines the height/width, in logical coordinates, |
| // of the ellipse used to draw the rounded corners. |
| int height = leis.readShort(); |
| int width = leis.readShort(); |
| corners.setSize(width, height); |
| return 2*LittleEndianConsts.SHORT_SIZE+readBounds(leis, bounds); |
| } |
| |
| @Override |
| public void draw(HwmfGraphics ctx) { |
| ctx.fill(getShape()); |
| } |
| |
| protected RoundRectangle2D getShape() { |
| return new RoundRectangle2D.Double( |
| bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight(), |
| corners.getWidth(), corners.getHeight() |
| ); |
| } |
| |
| public Dimension2D getCorners() { |
| return corners; |
| } |
| |
| public Rectangle2D getBounds() { |
| return bounds; |
| } |
| |
| @Override |
| public Map<String, Supplier<?>> getGenericProperties() { |
| return GenericRecordUtil.getGenericProperties( |
| "bounds", this::getBounds, |
| "corners", this::getCorners |
| ); |
| } |
| |
| } |
| |
| |
| /** |
| * The META_ARC record draws an elliptical arc. |
| */ |
| public static class WmfArc implements HwmfRecord { |
| public enum WmfArcClosure { |
| ARC(HwmfRecordType.arc, Arc2D.OPEN, FillDrawStyle.DRAW), |
| CHORD(HwmfRecordType.chord, Arc2D.CHORD, FillDrawStyle.FILL_DRAW), |
| PIE(HwmfRecordType.pie, Arc2D.PIE, FillDrawStyle.FILL_DRAW); |
| |
| public final HwmfRecordType recordType; |
| public final int awtType; |
| public final FillDrawStyle drawStyle; |
| |
| WmfArcClosure(HwmfRecordType recordType, int awtType, FillDrawStyle drawStyle) { |
| this.recordType = recordType; |
| this.awtType = awtType; |
| this.drawStyle = drawStyle; |
| } |
| } |
| |
| /** starting point of the arc */ |
| protected final Point2D startPoint = new Point2D.Double(); |
| |
| /** ending point of the arc */ |
| protected final Point2D endPoint = new Point2D.Double(); |
| |
| /** the bounding rectangle */ |
| protected final Rectangle2D bounds = new Rectangle2D.Double(); |
| |
| |
| @Override |
| public HwmfRecordType getWmfRecordType() { |
| return HwmfRecordType.arc; |
| } |
| |
| @Override |
| public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { |
| readPointS(leis, endPoint); |
| readPointS(leis, startPoint); |
| readBounds(leis, bounds); |
| |
| return 8*LittleEndianConsts.SHORT_SIZE; |
| } |
| |
| @Override |
| public void draw(HwmfGraphics ctx) { |
| getFillDrawStyle().handler.accept(ctx, getShape()); |
| } |
| |
| public WmfArcClosure getArcClosure() { |
| switch (getWmfRecordType()) { |
| default: |
| case arc: |
| return WmfArcClosure.ARC; |
| case chord: |
| return WmfArcClosure.CHORD; |
| case pie: |
| return WmfArcClosure.PIE; |
| } |
| } |
| |
| protected FillDrawStyle getFillDrawStyle() { |
| return getArcClosure().drawStyle; |
| } |
| |
| protected Arc2D getShape() { |
| double startAngle = Math.toDegrees(Math.atan2(-(startPoint.getY() - bounds.getCenterY()), startPoint.getX() - bounds.getCenterX())); |
| double endAngle = Math.toDegrees(Math.atan2(-(endPoint.getY() - bounds.getCenterY()), endPoint.getX() - bounds.getCenterX())); |
| double arcAngle = (endAngle - startAngle) + (endAngle - startAngle > 0 ? 0 : 360); |
| if (startAngle < 0) { |
| startAngle += 360; |
| } |
| |
| return new Arc2D.Double(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight(), |
| startAngle, arcAngle, getArcClosure().awtType); |
| } |
| |
| @Override |
| public String toString() { |
| return GenericRecordJsonWriter.marshal(this); |
| } |
| |
| public Point2D getStartPoint() { |
| return startPoint; |
| } |
| |
| public Point2D getEndPoint() { |
| return endPoint; |
| } |
| |
| public Rectangle2D getBounds() { |
| return bounds; |
| } |
| |
| @Override |
| public Map<String, Supplier<?>> getGenericProperties() { |
| final Arc2D arc = getShape(); |
| return GenericRecordUtil.getGenericProperties( |
| "startPoint", this::getStartPoint, |
| "endPoint", this::getEndPoint, |
| "startAngle", arc::getAngleStart, |
| "extentAngle", arc::getAngleExtent, |
| "bounds", this::getBounds |
| ); |
| } |
| } |
| |
| /** |
| * The META_PIE record draws a pie-shaped wedge bounded by the intersection of an ellipse and two |
| * radials. The pie is outlined by using the pen and filled by using the brush that are defined in the |
| * playback device context. |
| */ |
| public static class WmfPie extends WmfArc { |
| |
| @Override |
| public HwmfRecordType getWmfRecordType() { |
| return HwmfRecordType.pie; |
| } |
| } |
| |
| /** |
| * The META_CHORD record draws a chord, which is defined by a region bounded by the intersection of |
| * an ellipse with a line segment. The chord is outlined using the pen and filled using the brush |
| * that are defined in the playback device context. |
| */ |
| public static class WmfChord extends WmfArc { |
| |
| @Override |
| public HwmfRecordType getWmfRecordType() { |
| return HwmfRecordType.chord; |
| } |
| } |
| |
| |
| /** |
| * The META_SELECTOBJECT record specifies a graphics object for the playback device context. The |
| * new object replaces the previous object of the same type, unless if the previous object is a palette |
| * object. If the previous object is a palette object, then the META_SELECTPALETTE record must be |
| * used instead of the META_SELECTOBJECT record, as the META_SELECTOBJECT record does not |
| * support replacing the palette object type. |
| */ |
| public static class WmfSelectObject implements HwmfRecord { |
| |
| /** |
| * A 16-bit unsigned integer used to index into the WMF Object Table to |
| * get the object to be selected. |
| */ |
| protected int objectIndex; |
| |
| @Override |
| public HwmfRecordType getWmfRecordType() { |
| return HwmfRecordType.selectObject; |
| } |
| |
| @Override |
| public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { |
| objectIndex = leis.readUShort(); |
| return LittleEndianConsts.SHORT_SIZE; |
| } |
| |
| @Override |
| public void draw(HwmfGraphics ctx) { |
| ctx.applyObjectTableEntry(objectIndex); |
| } |
| |
| @Override |
| public String toString() { |
| return GenericRecordJsonWriter.marshal(this); |
| } |
| |
| public int getObjectIndex() { |
| return objectIndex; |
| } |
| |
| @Override |
| public Map<String, Supplier<?>> getGenericProperties() { |
| return GenericRecordUtil.getGenericProperties("objectIndex", this::getObjectIndex); |
| } |
| } |
| |
| @SuppressWarnings("DuplicatedCode") |
| static int readBounds(LittleEndianInputStream leis, Rectangle2D bounds) { |
| // The 16-bit signed integers that defines the corners of the bounding rectangle. |
| int bottom = leis.readShort(); |
| int right = leis.readShort(); |
| int top = leis.readShort(); |
| int left = leis.readShort(); |
| |
| int x = Math.min(left, right); |
| int y = Math.min(top, bottom); |
| int w = Math.abs(left - right - 1); |
| int h = Math.abs(top - bottom - 1); |
| |
| bounds.setRect(x, y, w, h); |
| |
| return 4 * LittleEndianConsts.SHORT_SIZE; |
| } |
| |
| @SuppressWarnings("DuplicatedCode") |
| static int readRectS(LittleEndianInputStream leis, Rectangle2D bounds) { |
| // The 16-bit signed integers that defines the corners of the bounding rectangle. |
| int left = leis.readShort(); |
| int top = leis.readShort(); |
| int right = leis.readShort(); |
| int bottom = leis.readShort(); |
| |
| int x = Math.min(left, right); |
| int y = Math.min(top, bottom); |
| int w = Math.abs(left - right - 1); |
| int h = Math.abs(top - bottom - 1); |
| |
| bounds.setRect(x, y, w, h); |
| |
| return 4 * LittleEndianConsts.SHORT_SIZE; |
| } |
| |
| static int readPointS(LittleEndianInputStream leis, Point2D point) { |
| // a signed integer that defines the x/y-coordinate, in logical units. |
| int y = leis.readShort(); |
| int x = leis.readShort(); |
| point.setLocation(x, y); |
| return 2*LittleEndianConsts.SHORT_SIZE; |
| } |
| |
| @Internal |
| public static Rectangle2D normalizeBounds(Rectangle2D bounds) { |
| return (bounds.getWidth() >= 0 && bounds.getHeight() >= 0) ? bounds |
| : new Rectangle2D.Double( |
| bounds.getWidth() >= 0 ? bounds.getMinX() : bounds.getMaxX(), |
| bounds.getHeight() >= 0 ? bounds.getMinY() : bounds.getMaxY(), |
| Math.abs(bounds.getWidth()), |
| Math.abs(bounds.getHeight()) |
| ); |
| } |
| } |