feature: 实现文字换行
This commit is contained in:
@@ -97,6 +97,8 @@ public class Controller implements Initializable {
|
|||||||
stage.setTitle("Hyperpoint - " + slideName);
|
stage.setTitle("Hyperpoint - " + slideName);
|
||||||
}
|
}
|
||||||
alert.showAndWait();
|
alert.showAndWait();
|
||||||
|
} else {
|
||||||
|
showWarning("无效名称", "幻灯片名称不能为空。");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -348,7 +350,7 @@ public class Controller implements Initializable {
|
|||||||
* 显示警告对话框
|
* 显示警告对话框
|
||||||
*/
|
*/
|
||||||
private void showWarning(String title, String message) {
|
private void showWarning(String title, String message) {
|
||||||
Alert alert = new Alert(Alert.AlertType.WARNING);
|
MyAlert alert = new MyAlert(Alert.AlertType.WARNING);
|
||||||
alert.setTitle(title);
|
alert.setTitle(title);
|
||||||
alert.setHeaderText(title);
|
alert.setHeaderText(title);
|
||||||
alert.setContentText(message);
|
alert.setContentText(message);
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ public class DrawingCanvas extends Pane {
|
|||||||
setOnMouseDragged(this::handleMouseDragged);
|
setOnMouseDragged(this::handleMouseDragged);
|
||||||
setOnMouseReleased(this::handleMouseReleased);
|
setOnMouseReleased(this::handleMouseReleased);
|
||||||
|
|
||||||
|
|
||||||
setStyle("-fx-border-color: #e0e0e0; -fx-border-width: 1;");
|
setStyle("-fx-border-color: #e0e0e0; -fx-border-width: 1;");
|
||||||
|
|
||||||
// 绑定Canvas大小到Pane大小
|
// 绑定Canvas大小到Pane大小
|
||||||
@@ -281,14 +282,27 @@ public class DrawingCanvas extends Pane {
|
|||||||
* 绘制文本对象
|
* 绘制文本对象
|
||||||
*/
|
*/
|
||||||
private void drawTextObject(GraphicsContext gc, TextObject textObj, boolean selected) {
|
private void drawTextObject(GraphicsContext gc, TextObject textObj, boolean selected) {
|
||||||
gc.setFill(textObj.getTextColorAsColor());
|
|
||||||
|
|
||||||
// 设置字体和风格
|
// 设置字体和风格
|
||||||
Font font = createFont(textObj.getFontFamily(), textObj.getFontSize(), textObj.getFontStyle());
|
Font font = createFont(textObj.getFontFamily(), textObj.getFontSize(), textObj.getFontStyle());
|
||||||
gc.setFont(font);
|
gc.setFont(font);
|
||||||
|
|
||||||
// 绘制文本
|
// 绘制背景
|
||||||
gc.fillText(textObj.getText(), textObj.getX(), textObj.getY() + textObj.getFontSize());
|
gc.setFill(Color.WHITE);
|
||||||
|
gc.fillRect(textObj.getX(), textObj.getY(), textObj.getWidth(), textObj.getHeight());
|
||||||
|
gc.setStroke(Color.LIGHTGRAY);
|
||||||
|
gc.strokeRect(textObj.getX(), textObj.getY(), textObj.getWidth(), textObj.getHeight());
|
||||||
|
|
||||||
|
// 设置文字颜色
|
||||||
|
gc.setFill(textObj.getTextColorAsColor());
|
||||||
|
|
||||||
|
// 获取排版后的文本行并绘制
|
||||||
|
double lineHeight = textObj.getFontSize() * 1.2;
|
||||||
|
double currentY = textObj.getY() + 5;
|
||||||
|
|
||||||
|
for (String line : textObj.getLayoutLines()) {
|
||||||
|
currentY += lineHeight;
|
||||||
|
gc.fillText(line, textObj.getX() + 5, currentY);
|
||||||
|
}
|
||||||
|
|
||||||
// 如果选中,绘制边框
|
// 如果选中,绘制边框
|
||||||
if (selected) {
|
if (selected) {
|
||||||
|
|||||||
@@ -1,6 +1,13 @@
|
|||||||
package dev.bytevibe.hyperpoint;
|
package dev.bytevibe.hyperpoint;
|
||||||
|
|
||||||
import javafx.scene.paint.Color;
|
import javafx.scene.paint.Color;
|
||||||
|
import javafx.scene.text.Font;
|
||||||
|
import javafx.scene.text.FontPosture;
|
||||||
|
import javafx.scene.text.FontWeight;
|
||||||
|
import javafx.scene.text.Text;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 文本对象
|
* 文本对象
|
||||||
@@ -9,9 +16,13 @@ public class TextObject extends DrawableObject {
|
|||||||
private String text;
|
private String text;
|
||||||
private String fontFamily;
|
private String fontFamily;
|
||||||
private double fontSize;
|
private double fontSize;
|
||||||
private String fontStyle; // NORMAL, ITALIC, BOLD
|
private String fontStyle; // NORMAL, ITALIC, BOLD, BOLD_ITALIC
|
||||||
private String textColor;
|
private String textColor;
|
||||||
|
|
||||||
|
// 缓存的排版结果
|
||||||
|
private List<String> layoutLines;
|
||||||
|
private boolean layoutDirty = true;
|
||||||
|
|
||||||
public TextObject(double x, double y, String text) {
|
public TextObject(double x, double y, String text) {
|
||||||
super(x, y, 200, 50);
|
super(x, y, 200, 50);
|
||||||
this.text = text;
|
this.text = text;
|
||||||
@@ -19,6 +30,7 @@ public class TextObject extends DrawableObject {
|
|||||||
this.fontSize = 16;
|
this.fontSize = 16;
|
||||||
this.fontStyle = "NORMAL";
|
this.fontStyle = "NORMAL";
|
||||||
this.textColor = "000000"; // 黑色
|
this.textColor = "000000"; // 黑色
|
||||||
|
this.layoutLines = new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getText() {
|
public String getText() {
|
||||||
@@ -27,6 +39,7 @@ public class TextObject extends DrawableObject {
|
|||||||
|
|
||||||
public void setText(String text) {
|
public void setText(String text) {
|
||||||
this.text = text;
|
this.text = text;
|
||||||
|
markLayoutDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getFontFamily() {
|
public String getFontFamily() {
|
||||||
@@ -35,6 +48,7 @@ public class TextObject extends DrawableObject {
|
|||||||
|
|
||||||
public void setFontFamily(String fontFamily) {
|
public void setFontFamily(String fontFamily) {
|
||||||
this.fontFamily = fontFamily;
|
this.fontFamily = fontFamily;
|
||||||
|
markLayoutDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
public double getFontSize() {
|
public double getFontSize() {
|
||||||
@@ -43,6 +57,7 @@ public class TextObject extends DrawableObject {
|
|||||||
|
|
||||||
public void setFontSize(double fontSize) {
|
public void setFontSize(double fontSize) {
|
||||||
this.fontSize = fontSize;
|
this.fontSize = fontSize;
|
||||||
|
markLayoutDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getFontStyle() {
|
public String getFontStyle() {
|
||||||
@@ -51,6 +66,7 @@ public class TextObject extends DrawableObject {
|
|||||||
|
|
||||||
public void setFontStyle(String fontStyle) {
|
public void setFontStyle(String fontStyle) {
|
||||||
this.fontStyle = fontStyle;
|
this.fontStyle = fontStyle;
|
||||||
|
markLayoutDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getTextColor() {
|
public String getTextColor() {
|
||||||
@@ -65,6 +81,188 @@ public class TextObject extends DrawableObject {
|
|||||||
return Color.web("#" + textColor);
|
return Color.web("#" + textColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 标记排版为脏,需要重新计算
|
||||||
|
*/
|
||||||
|
public void markLayoutDirty() {
|
||||||
|
layoutDirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取排版后的文本行
|
||||||
|
* 当宽度或高度改变时应该调用此方法重新计算
|
||||||
|
*/
|
||||||
|
public List<String> getLayoutLines() {
|
||||||
|
if (layoutDirty) {
|
||||||
|
recalculateLayout();
|
||||||
|
layoutDirty = false;
|
||||||
|
}
|
||||||
|
return layoutLines;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重新计算文本排版
|
||||||
|
*/
|
||||||
|
private void recalculateLayout() {
|
||||||
|
layoutLines.clear();
|
||||||
|
|
||||||
|
if (text == null || text.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建字体用于测量
|
||||||
|
Font font = createFont(fontFamily, fontSize, fontStyle);
|
||||||
|
double lineHeight = fontSize * 1.2;
|
||||||
|
double maxWidth = width - 10; // 减去左右边距
|
||||||
|
double maxHeight = height - 10; // 减去上下边距
|
||||||
|
int maxLines = Math.max(1, (int) (maxHeight / lineHeight));
|
||||||
|
|
||||||
|
// 检查文本是否包含中文
|
||||||
|
boolean hasChinese = containsChinese(text);
|
||||||
|
|
||||||
|
if (hasChinese) {
|
||||||
|
// 中文排版:逐字符处理,支持在任何字符处换行
|
||||||
|
layoutChinese(text, font, maxWidth, maxHeight, maxLines);
|
||||||
|
} else {
|
||||||
|
// 英文排版:基于空格分词
|
||||||
|
layoutEnglish(text, font, maxWidth, maxHeight, maxLines);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查文本是否包含中文字符
|
||||||
|
*/
|
||||||
|
private boolean containsChinese(String text) {
|
||||||
|
for (char c : text.toCharArray()) {
|
||||||
|
// Unicode中文范围:
|
||||||
|
// \u4E00-\u9FFF: CJK统一表意文字
|
||||||
|
// \u3400-\u4DBF: CJK扩展A
|
||||||
|
// \uF900-\uFAFF: CJK兼容表意文字
|
||||||
|
if ((c >= '\u4E00' && c <= '\u9FFF') ||
|
||||||
|
(c >= '\u3400' && c <= '\u4DBF') ||
|
||||||
|
(c >= '\uF900' && c <= '\uFAFF')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 中文文本排版(逐字符处理)
|
||||||
|
*/
|
||||||
|
private void layoutChinese(String text, Font font, double maxWidth, double maxHeight, int maxLines) {
|
||||||
|
StringBuilder line = new StringBuilder();
|
||||||
|
|
||||||
|
for (int i = 0; i < text.length(); i++) {
|
||||||
|
char c = text.charAt(i);
|
||||||
|
|
||||||
|
// 测试添加当前字符后的行宽
|
||||||
|
String testLine = line.toString() + c;
|
||||||
|
Text textNode = new Text(testLine);
|
||||||
|
textNode.setFont(font);
|
||||||
|
double lineWidth = textNode.getLayoutBounds().getWidth();
|
||||||
|
|
||||||
|
// 如果超过最大宽度
|
||||||
|
if (lineWidth > maxWidth && !line.toString().isEmpty()) {
|
||||||
|
// 当前行满,添加到结果
|
||||||
|
layoutLines.add(line.toString());
|
||||||
|
line = new StringBuilder(String.valueOf(c));
|
||||||
|
|
||||||
|
// 检查是否超过最大行数
|
||||||
|
if (layoutLines.size() >= maxLines) {
|
||||||
|
// 在最后一行添加省略号
|
||||||
|
if (layoutLines.size() > 0) {
|
||||||
|
String lastLine = layoutLines.get(layoutLines.size() - 1);
|
||||||
|
if (lastLine.length() > 1) {
|
||||||
|
lastLine = lastLine.substring(0, lastLine.length() - 1) + "…";
|
||||||
|
layoutLines.set(layoutLines.size() - 1, lastLine);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 当前字符添加到行
|
||||||
|
line.append(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加最后一行
|
||||||
|
if (!line.toString().isEmpty()) {
|
||||||
|
layoutLines.add(line.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 英文文本排版(基于空格分词)
|
||||||
|
*/
|
||||||
|
private void layoutEnglish(String text, Font font, double maxWidth, double maxHeight, int maxLines) {
|
||||||
|
String[] words = text.split(" ");
|
||||||
|
StringBuilder line = new StringBuilder();
|
||||||
|
|
||||||
|
for (String word : words) {
|
||||||
|
// 获取添加单词后的宽度
|
||||||
|
String testLine = line.toString().isEmpty() ? word : line + " " + word;
|
||||||
|
Text textNode = new Text(testLine);
|
||||||
|
textNode.setFont(font);
|
||||||
|
double lineWidth = textNode.getLayoutBounds().getWidth();
|
||||||
|
|
||||||
|
// 如果超过最大宽度,换行
|
||||||
|
if (lineWidth > maxWidth && !line.toString().isEmpty()) {
|
||||||
|
layoutLines.add(line.toString());
|
||||||
|
line = new StringBuilder(word);
|
||||||
|
|
||||||
|
// 检查是否超过最大行数
|
||||||
|
if (layoutLines.size() >= maxLines) {
|
||||||
|
// 在最后一行添加省略号
|
||||||
|
if (!layoutLines.isEmpty()) {
|
||||||
|
String lastLine = layoutLines.get(layoutLines.size() - 1);
|
||||||
|
if (lastLine.length() > 1) {
|
||||||
|
lastLine = lastLine.substring(0, lastLine.length() - 1) + "…";
|
||||||
|
layoutLines.set(layoutLines.size() - 1, lastLine);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (line.toString().isEmpty()) {
|
||||||
|
line = new StringBuilder(word);
|
||||||
|
} else {
|
||||||
|
line.append(" ").append(word);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加最后一行
|
||||||
|
if (!line.toString().isEmpty()) {
|
||||||
|
layoutLines.add(line.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建字体对象
|
||||||
|
*/
|
||||||
|
private Font createFont(String family, double size, String style) {
|
||||||
|
FontWeight weight = style.contains("BOLD") ? FontWeight.BOLD : FontWeight.NORMAL;
|
||||||
|
FontPosture posture = style.contains("ITALIC") ? FontPosture.ITALIC : FontPosture.REGULAR;
|
||||||
|
return Font.font(family, weight, posture, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setWidth(double width) {
|
||||||
|
if (this.width != width) {
|
||||||
|
super.setWidth(width);
|
||||||
|
markLayoutDirty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setHeight(double height) {
|
||||||
|
if (this.height != height) {
|
||||||
|
super.setHeight(height);
|
||||||
|
markLayoutDirty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean contains(double px, double py) {
|
public boolean contains(double px, double py) {
|
||||||
return px >= x && px <= x + width && py >= y && py <= y + height;
|
return px >= x && px <= x + width && py >= y && py <= y + height;
|
||||||
@@ -76,3 +274,6 @@ public class TextObject extends DrawableObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user