feature: 实现文字换行
This commit is contained in:
@@ -97,6 +97,8 @@ public class Controller implements Initializable {
|
||||
stage.setTitle("Hyperpoint - " + slideName);
|
||||
}
|
||||
alert.showAndWait();
|
||||
} else {
|
||||
showWarning("无效名称", "幻灯片名称不能为空。");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -348,7 +350,7 @@ public class Controller implements Initializable {
|
||||
* 显示警告对话框
|
||||
*/
|
||||
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.setHeaderText(title);
|
||||
alert.setContentText(message);
|
||||
|
||||
@@ -41,6 +41,7 @@ public class DrawingCanvas extends Pane {
|
||||
setOnMouseDragged(this::handleMouseDragged);
|
||||
setOnMouseReleased(this::handleMouseReleased);
|
||||
|
||||
|
||||
setStyle("-fx-border-color: #e0e0e0; -fx-border-width: 1;");
|
||||
|
||||
// 绑定Canvas大小到Pane大小
|
||||
@@ -281,14 +282,27 @@ public class DrawingCanvas extends Pane {
|
||||
* 绘制文本对象
|
||||
*/
|
||||
private void drawTextObject(GraphicsContext gc, TextObject textObj, boolean selected) {
|
||||
gc.setFill(textObj.getTextColorAsColor());
|
||||
|
||||
// 设置字体和风格
|
||||
Font font = createFont(textObj.getFontFamily(), textObj.getFontSize(), textObj.getFontStyle());
|
||||
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) {
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
package dev.bytevibe.hyperpoint;
|
||||
|
||||
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 fontFamily;
|
||||
private double fontSize;
|
||||
private String fontStyle; // NORMAL, ITALIC, BOLD
|
||||
private String fontStyle; // NORMAL, ITALIC, BOLD, BOLD_ITALIC
|
||||
private String textColor;
|
||||
|
||||
// 缓存的排版结果
|
||||
private List<String> layoutLines;
|
||||
private boolean layoutDirty = true;
|
||||
|
||||
public TextObject(double x, double y, String text) {
|
||||
super(x, y, 200, 50);
|
||||
this.text = text;
|
||||
@@ -19,6 +30,7 @@ public class TextObject extends DrawableObject {
|
||||
this.fontSize = 16;
|
||||
this.fontStyle = "NORMAL";
|
||||
this.textColor = "000000"; // 黑色
|
||||
this.layoutLines = new ArrayList<>();
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
@@ -27,6 +39,7 @@ public class TextObject extends DrawableObject {
|
||||
|
||||
public void setText(String text) {
|
||||
this.text = text;
|
||||
markLayoutDirty();
|
||||
}
|
||||
|
||||
public String getFontFamily() {
|
||||
@@ -35,6 +48,7 @@ public class TextObject extends DrawableObject {
|
||||
|
||||
public void setFontFamily(String fontFamily) {
|
||||
this.fontFamily = fontFamily;
|
||||
markLayoutDirty();
|
||||
}
|
||||
|
||||
public double getFontSize() {
|
||||
@@ -43,6 +57,7 @@ public class TextObject extends DrawableObject {
|
||||
|
||||
public void setFontSize(double fontSize) {
|
||||
this.fontSize = fontSize;
|
||||
markLayoutDirty();
|
||||
}
|
||||
|
||||
public String getFontStyle() {
|
||||
@@ -51,6 +66,7 @@ public class TextObject extends DrawableObject {
|
||||
|
||||
public void setFontStyle(String fontStyle) {
|
||||
this.fontStyle = fontStyle;
|
||||
markLayoutDirty();
|
||||
}
|
||||
|
||||
public String getTextColor() {
|
||||
@@ -65,6 +81,188 @@ public class TextObject extends DrawableObject {
|
||||
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
|
||||
public boolean contains(double px, double py) {
|
||||
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