001/* 002 * ModelNote -- Holds a textual note String located at a point in model space with a specified 003 * size and orientation in model space. 004 * 005 * Copyright (C) 2014-2025, Joseph A. Huwaldt 006 * All rights reserved. 007 * 008 * This library is free software; you can redistribute it and/or 009 * modify it under the terms of the GNU Lesser General Public 010 * License as published by the Free Software Foundation; either 011 * version 2.1 of the License, or (at your option) any later version. 012 * 013 * This library is distributed in the hope that it will be useful, 014 * but WITHOUT ANY WARRANTY; without even the implied warranty of 015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 016 * Lesser General Public License for more details. 017 * 018 * You should have received a copy of the GNU Lesser General Public License 019 * along with this program; if not, write to the Free Software 020 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 021 * Or visit: http://www.gnu.org/licenses/lgpl.html 022 */ 023package geomss.geom; 024 025import jahuwaldt.js.param.DCMatrix; 026import jahuwaldt.js.param.Parameter; 027import jahuwaldt.js.param.Rotation; 028import java.awt.Font; 029import java.text.MessageFormat; 030import java.util.Objects; 031import static java.util.Objects.requireNonNull; 032import javax.measure.converter.ConversionException; 033import javax.measure.quantity.Dimensionless; 034import javax.measure.quantity.Length; 035import javax.measure.unit.Unit; 036import javolution.context.ObjectFactory; 037import javolution.lang.ValueType; 038import javolution.xml.XMLFormat; 039import javolution.xml.stream.XMLStreamException; 040import org.jscience.mathematics.vector.Float64Vector; 041 042/** 043 * Represents a textual note located at a point in model space with a specified size and 044 * orientation in model space. 045 * 046 * <p> 047 * Modified by: Joseph A. Huwaldt </p> 048 * 049 * @author Joseph A. Huwaldt, Date: February 10, 2014 050 * @version February 17, 2015 051 */ 052@SuppressWarnings({"serial", "CloneableImplementsClone"}) 053public final class ModelNote extends GenModelNote implements ValueType { 054 055 /** 056 * The text string displayed in the note. 057 */ 058 private String text; 059 060 /** 061 * Holds the direction that the text flows in space. 062 */ 063 private Vector<Dimensionless> xhat; 064 065 /** 066 * Holds the direction, normal to xhat, that represents the vertical direction of the 067 * text (the text ascent direction). 068 */ 069 private Vector<Dimensionless> yhat; 070 071 /** 072 * Holds the location of the note in model space. 073 */ 074 private Point location; 075 076 /** 077 * The font used to display the note. 078 */ 079 private Font font; 080 081 /** 082 * The approximate height of the text in model units. 083 */ 084 private Parameter<Length> height; 085 086 /** 087 * Construct and return a new instance of a ModelNote that uses the specified text 088 * string, the specified display font, is located at the location and orientation in 089 * space specified by the input vectors, and has the specified height in model units. 090 * 091 * @param text The text to be displayed in this geometry object. May not be null. 092 * @param xhat The vector indicating the direction that left-to-right text flows 093 * in space. May not be null. 094 * @param yhat The vector, orthogonal to xhat, indicating the vertical direction 095 * (or ascent) of the text. May not be null. 096 * @param location The location of this geometry object in model space. May not be null. 097 * @param font The font used to display this note. May not be null. 098 * @param height The height of the text box in model units. May not be null. 099 * @return A new ModelNote using the specified inputs. 100 */ 101 public static ModelNote valueOf(CharSequence text, 102 GeomVector<Dimensionless> xhat, GeomVector<Dimensionless> yhat, GeomPoint location, 103 Font font, Parameter<Length> height) { 104 requireNonNull(text); 105 requireNonNull(font); 106 107 // Make sure the physical dimension and units are compatible. 108 Unit<Length> unit = height.getUnit(); 109 int dim = GeomUtil.maxPhyDimension(requireNonNull(xhat), requireNonNull(yhat), requireNonNull(location)); 110 if (dim < 2) 111 throw new IllegalArgumentException( 112 MessageFormat.format(RESOURCES.getString("dimensionNotAtLeast2"), "ModelNote", dim)); 113 xhat = xhat.toDimension(dim); 114 yhat = yhat.toDimension(dim); 115 location = location.toDimension(dim).to(unit); 116 117 // Make certain that yhat is orthogonal to xhat. 118 GeomVector n = xhat.cross(yhat); 119 yhat = n.cross(xhat).toUnitVector(); 120 121 ModelNote note = FACTORY.object(); 122 note.text = text.toString(); 123 note.xhat = xhat.immutable(); 124 note.yhat = yhat.immutable(); 125 note.location = location.immutable(); 126 note.font = font; 127 note.height = height; 128 129 return note; 130 } 131 132 /** 133 * Construct and return a new instance of a ModelNote that uses the specified text 134 * string, the default display font at the specified size, and is located at the 135 * location and orientation in space specified by the input plane. 136 * 137 * @param text The text to be displayed in this geometry object. May not be null. 138 * @param xhat The vector indicating the direction that left-to-right text flows 139 * in space. May not be null. 140 * @param yhat The vector, orthogonal to xhat, indicating the vertical direction 141 * (or ascent) of the text. May not be null. 142 * @param location The location of this geometry object in model space. May not be null. 143 * @param height The height of the text box in model units. May not be null. 144 * @return A new ModelNote using the specified inputs. 145 */ 146 public static ModelNote valueOf(CharSequence text, 147 GeomVector<Dimensionless> xhat, GeomVector<Dimensionless> yhat, GeomPoint location, 148 Parameter<Length> height) { 149 return ModelNote.valueOf(text, xhat, yhat, location, DEFAULT_FONT, height); 150 } 151 152 /** 153 * Construct and return a new instance of a ModelNote that uses the specified text 154 * string, the default display font at the specified size, and is located parallel to 155 * the the XY plane with the xhat in the X-axis direction, yhat in the Y-axis 156 * direction. 157 * 158 * @param text The text to be displayed in this geometry object. May not be null. 159 * @param location The location of this geometry object in model space. May not be null. 160 * @param height The height of the text box in model units. May not be null. 161 * @return A new ModelNote using the specified inputs. 162 */ 163 public static ModelNote valueOf(CharSequence text, GeomPoint location, Parameter<Length> height) { 164 int dim = location.getPhyDimension(); 165 if (dim < 2) 166 throw new IllegalArgumentException( 167 MessageFormat.format(RESOURCES.getString("dimensionNotAtLeast2"), "ModelNote", dim)); 168 169 Vector<Dimensionless> xhat = Vector.valueOf(1, 0).toDimension(dim); 170 Vector<Dimensionless> yhat = Vector.valueOf(0, 1).toDimension(dim); 171 return ModelNote.valueOf(text, xhat, yhat, location, DEFAULT_FONT, height); 172 } 173 174 /** 175 * Construct and return a new instance of a ModelNote that uses the specified text 176 * string, the default display font at the specified size, and is located at the 177 * specified location with the text plane oriented using the specified orientation 178 * rotation. 179 * 180 * @param text The text to be displayed in this geometry object. May not be 181 * null. 182 * @param orientation The orientation of the text relative to X,Y & Z 3D space axes. 183 * May not be null. 184 * @param location The location of this geometry object in model space. The 185 * physical dimension must be ≤ 3. If it is less than 3, it will 186 * be increased to 3. If > 3, an exception is thrown. May not be 187 * null. 188 * @param height The height of the text box in model units. May not be null. 189 * @throws IllegalArgumentException if the physical dimension of the input location 190 * point is greater than 3. 191 * @return A new ModelNote using the specified inputs. 192 */ 193 public static ModelNote valueOf(CharSequence text, Rotation orientation, GeomPoint location, Parameter<Length> height) { 194 int dim = location.getPhyDimension(); 195 if (dim < 3) 196 location = location.toDimension(3); 197 else if (dim > 3) 198 throw new IllegalArgumentException( 199 MessageFormat.format(RESOURCES.getString("rotationDimensionLTE3"), dim)); 200 201 // Convert the arbitrary rotation to a direction-cosine matrix. 202 DCMatrix dcm = orientation.toDCM(); 203 204 // Convert the columns of the DCM matrix to xhat & yhat 205 Float64Vector col0 = dcm.getColumn(Point.X); 206 Float64Vector col1 = dcm.getColumn(Point.Y); 207 Vector<Dimensionless> xhat = Vector.valueOf(col0, Dimensionless.UNIT); 208 Vector<Dimensionless> yhat = Vector.valueOf(col1, Dimensionless.UNIT); 209 210 return ModelNote.valueOf(text, xhat, yhat, location, height); 211 } 212 213 /** 214 * Returns a new ModelNote instance that is identical to the specified ModelNote. 215 * 216 * @param note the ModelNote to be copied into a new ModelNote. May not be null. 217 * @return A new ModelNote identical to the input note (a copy or clone). 218 */ 219 public static ModelNote valueOf(ModelNote note) { 220 return copyOf(requireNonNull(note)); 221 } 222 223 /** 224 * Return the text string associated with this note object. 225 * 226 * @return The text string associated with this note object. 227 */ 228 @Override 229 public String getNote() { 230 return text; 231 } 232 233 /** 234 * Return the vector indicating the horizontal axis direction for the text. 235 * 236 * @return The vector indicating the horizontal axis direction for the text. 237 */ 238 @Override 239 public Vector<Dimensionless> getXHat() { 240 return xhat; 241 } 242 243 /** 244 * Return the vector indicating the vertical axis direction (or ascent direction) for 245 * the text. 246 * 247 * @return The vector indicating the vertical axis direction (or ascent direction) for 248 * the text. 249 */ 250 @Override 251 public Vector<Dimensionless> getYHat() { 252 return yhat; 253 } 254 255 /** 256 * Return the location of this note in space. 257 * 258 * @return The location of this note in space. 259 */ 260 @Override 261 public Point getLocation() { 262 return location; 263 } 264 265 /** 266 * Return the height of the text box in model units. 267 * 268 * @return The height of the text box in model units. 269 */ 270 @Override 271 public Parameter<Length> getHeight() { 272 return height; 273 } 274 275 /** 276 * Return the font used to display this note. 277 * 278 * @return The font used to display this note. 279 */ 280 @Override 281 public Font getFont() { 282 return font; 283 } 284 285 /** 286 * Return an immutable version of this note. 287 * 288 * @return An immutable version of this note. 289 */ 290 @Override 291 public ModelNote immutable() { 292 return this; 293 } 294 295 /** 296 * Return a new note object identical to this one, but with the specified font. 297 * 298 * @param font The font for the new copy of this note. May not be null. 299 * @return A new note object identical to this one, but with the specified font. 300 */ 301 @Override 302 public ModelNote changeFont(Font font) { 303 ModelNote note = ModelNote.valueOf(text, xhat, yhat, location, requireNonNull(font), height); 304 copyState(note); 305 return note; 306 } 307 308 /** 309 * Return a new note object identical to this one, but with the specified location in 310 * model space. The returned note will have a different physical dimension from this 311 * one if the input location has a different physical dimension from the original 312 * location. 313 * 314 * @param location The location for the new copy of this note. May note be null. 315 * @return A new note object identical to this one, but with the specified location in 316 * model space. 317 */ 318 @Override 319 public ModelNote changeLocation(GeomPoint location) { 320 ModelNote note = ModelNote.valueOf(text, xhat, yhat, requireNonNull(location), font, height); 321 copyState(note); 322 return note; 323 } 324 325 /** 326 * Return a new note object identical to this one, but with the specified height in 327 * model space. 328 * 329 * @param height The height of the new copy of this note. May note be null. 330 * @return A new note object identical to this one, but with the specified height in 331 * model space. 332 */ 333 @Override 334 public ModelNote changeHeight(Parameter<Length> height) { 335 ModelNote note = ModelNote.valueOf(text, xhat, yhat, location, font, height.to(getUnit())); 336 copyState(note); 337 return note; 338 } 339 340 /** 341 * Returns the number of physical dimensions of the geometry element. This 342 * implementation will return the physical dimensions of the plane indicating the 343 * location and orientation of the note in space. 344 * 345 * @return The number of physical dimensions of the geometry element. 346 */ 347 @Override 348 public int getPhyDimension() { 349 return location.getPhyDimension(); 350 } 351 352 /** 353 * Return <code>true</code> if this ModelNote contains valid and finite numerical 354 * components. A value of <code>false</code> will be returned if any of the location 355 * coordinate values are NaN or Inf. 356 * 357 * @return true if this ModelNote contains valid and finite numerical components. 358 */ 359 @Override 360 public boolean isValid() { 361 return xhat.isValid() && yhat.isValid() && location.isValid(); 362 } 363 364 /** 365 * Returns a copy of this ModelNote instance 366 * {@link javolution.context.AllocatorContext allocated} by the calling thread 367 * (possibly on the stack). 368 * 369 * @return an identical and independent copy of this note. 370 */ 371 @Override 372 public ModelNote copy() { 373 return copyOf(this); 374 } 375 376 /** 377 * Return a copy of this object with any transformations or subranges removed 378 * (applied). 379 * 380 * @return A copy of this object with any transformations or subranges removed. 381 */ 382 @Override 383 public ModelNote copyToReal() { 384 return copy(); 385 } 386 387 /** 388 * Returns the unit in which the note height and location are stored. 389 * 390 * @return The unit in which the note height and location are stored. 391 */ 392 @Override 393 public final Unit<Length> getUnit() { 394 return height.getUnit(); 395 } 396 397 /** 398 * Returns the equivalent to this note but with the location stated in the specified 399 * unit. 400 * 401 * @param unit the length unit of the note to be returned. May not be null. 402 * @return an equivalent of this note but with location stated in the specified unit. 403 * @throws ConversionException if the the input unit is not a length unit. 404 */ 405 @Override 406 public ModelNote to(Unit<Length> unit) throws ConversionException { 407 if (unit.equals(getUnit())) 408 return this; 409 410 ModelNote note = FACTORY.object(); 411 note.text = text; 412 note.xhat = xhat; 413 note.yhat = yhat; 414 note.location = location.to(unit); 415 note.height = height.to(unit); 416 copyState(note); 417 418 return note; 419 } 420 421 /** 422 * Return the equivalent of this note converted to the specified number of physical 423 * dimensions. If the number of dimensions is greater than this element, then zeros 424 * are added to the additional dimensions. If the number of dimensions is less than 425 * this element, then the extra dimensions are simply dropped (truncated). If the new 426 * dimensions are the same as the dimension of this element, then this element is 427 * simply returned. 428 * 429 * @param newDim The dimension of the note to return. 430 * @return The equivalent to this note converted to the new dimensions. 431 */ 432 @Override 433 public ModelNote toDimension(int newDim) { 434 int thisDim = this.getPhyDimension(); 435 if (newDim == thisDim) 436 return this; 437 if (newDim < 2) 438 throw new IllegalArgumentException( 439 MessageFormat.format(RESOURCES.getString("dimensionNotAtLeast2"), "ModelNote", newDim)); 440 441 // Convert the underlying geometry. 442 Vector<Dimensionless> newXHat = xhat.toDimension(newDim); 443 Vector<Dimensionless> newYHat = yhat.toDimension(newDim); 444 Point newLoc = location.toDimension(newDim); 445 446 // Create and return a new note with the new geometry. 447 ModelNote note = ModelNote.valueOf(text, newXHat, newYHat, newLoc, font, height); 448 copyState(note); 449 450 return note; 451 } 452 453 /** 454 * Compares this ModelNote against the specified object for strict equality (same 455 * values and same units). 456 * 457 * @param obj the object to compare with. 458 * @return <code>true</code> if this note is identical to that note; 459 * <code>false</code> otherwise. 460 */ 461 @Override 462 public boolean equals(Object obj) { 463 if (this == obj) 464 return true; 465 if ((obj == null) || (obj.getClass() != this.getClass())) 466 return false; 467 468 ModelNote that = (ModelNote)obj; 469 return this.text.equals(that.text) 470 && this.height.equals(that.height) 471 && this.xhat.equals(that.xhat) 472 && this.yhat.equals(that.yhat) 473 && this.location.equals(that.location) 474 && super.equals(obj); 475 } 476 477 /** 478 * Returns the hash code for this ModelNote object. 479 * 480 * @return the hash code value. 481 */ 482 @Override 483 public int hashCode() { 484 return 31*super.hashCode() + Objects.hash(text, xhat, yhat, location, height); 485 } 486 487 /** 488 * Holds the default XML representation for this object. 489 */ 490 @SuppressWarnings("FieldNameHidesFieldInSuperclass") 491 protected static final XMLFormat<ModelNote> XML = new XMLFormat<ModelNote>(ModelNote.class) { 492 493 @Override 494 public ModelNote newInstance(Class<ModelNote> cls, XMLFormat.InputElement xml) throws XMLStreamException { 495 return FACTORY.object(); 496 } 497 498 @Override 499 public void read(XMLFormat.InputElement xml, ModelNote obj) throws XMLStreamException { 500 // Read in the font information. 501 String fontStr = xml.getAttribute("font", DEFAULT_FONT_CODE); 502 Font font = Font.decode(fontStr); 503 504 GenModelNote.XML.read(xml, obj); // Call parent read. 505 506 // Read in the text string. 507 String text = xml.get("Note", String.class); 508 509 // Read in the height. 510 Parameter<Length> height = xml.get("Height", Parameter.class); 511 512 // Read in the location & orientation of the note in model space. 513 Vector<Dimensionless> xhat = xml.get("XHat", Vector.class); 514 Vector<Dimensionless> yhat = xml.get("YHat", Vector.class); 515 Point location = xml.get("Location", Point.class); 516 517 // Make sure the physical dimensions and units are consistent. 518 Unit<Length> unit = height.getUnit(); 519 int dim = GeomUtil.maxPhyDimension(xhat, yhat, location); 520 if (dim < 2) 521 throw new XMLStreamException( 522 MessageFormat.format(RESOURCES.getString("dimensionNotAtLeast2"), "ModelNote", dim)); 523 xhat = xhat.toDimension(dim); 524 yhat = yhat.toDimension(dim); 525 location = location.toDimension(dim).to(unit); 526 527 // Fill in the object definition. 528 obj.text = text; 529 obj.font = font; 530 obj.xhat = xhat; 531 obj.yhat = yhat; 532 obj.location = location; 533 obj.height = height; 534 } 535 536 @Override 537 public void write(ModelNote obj, XMLFormat.OutputElement xml) throws XMLStreamException { 538 // Write out a font string. 539 Font font = obj.getFont(); 540 String fontStr = encodeFont(font.getName(), font.getStyle(), font.getSize()); 541 xml.setAttribute("font", fontStr); 542 543 GenModelNote.XML.write(obj, xml); // Call parent write. 544 545 // Write out the text string. 546 xml.add(obj.getNote(), "Note", String.class); 547 548 // Write out note height. 549 xml.add(obj.getHeight(), "Height", Parameter.class); 550 551 // Write out the location & orientation of the note in model space. 552 xml.add(obj.getXHat(), "XHat", Vector.class); 553 xml.add(obj.getYHat(), "YHat", Vector.class); 554 xml.add(obj.getLocation(), "Location", Point.class); 555 } 556 }; 557 558 /////////////////////// 559 // Factory creation. // 560 /////////////////////// 561 private ModelNote() { } 562 563 @SuppressWarnings("unchecked") 564 private static ModelNote copyOf(ModelNote original) { 565 ModelNote obj = FACTORY.object(); 566 obj.text = original.text; 567 obj.xhat = original.xhat.copy(); 568 obj.yhat = original.yhat.copy(); 569 obj.location = original.location.copy(); 570 obj.height = original.height.copy(); 571 obj.font = original.font; 572 original.copyState(obj); 573 return obj; 574 } 575 576 @SuppressWarnings("unchecked") 577 private static final ObjectFactory<ModelNote> FACTORY = new ObjectFactory<ModelNote>() { 578 @Override 579 protected ModelNote create() { 580 return new ModelNote(); 581 } 582 583 @Override 584 protected void cleanup(ModelNote obj) { 585 obj.reset(); 586 obj.text = null; 587 obj.xhat = null; 588 obj.yhat = null; 589 obj.location = null; 590 obj.height = null; 591 obj.font = null; 592 } 593 }; 594 595}