تخصيص أشكال العروض التقديمية في Python عبر Java
نظرة عامة
تشرح هذه المقالة كيفية تخصيص أشكال العروض التقديمية في Aspose.Slides عن طريق تعديل هندسة الشكل عبر نقاط التحرير ومسارات الهندسة. تُظهر كيفية العمل مع GeometryPath لتعديل الأشكال الحالية، وتنفيذ عمليات تحرير أساسية للمسار، وإضافة أو إزالة نقاط، وتطبيق الهندسة المحدثة مرة أخرى على الشكل.
كما توضح كيفية إنشاء أشكال مخصصة ومركبة، بناء أشكال ذات زوايا منحنية، تحديد ما إذا كانت هندسة الشكل مغلقة، فحص عناصر الشكل، وتحويل خطوط نص Java AWT إلى مسارات هندسية.
تغيير شكل باستخدام نقاط التحرير
في PowerPoint، يمكنك استخدام Edit Points لتعديل مربع:
- تحريك زاوية إلى الداخل أو الخارج.
- تعديل الانحناء عند نقطة.
- إضافة نقاط جديدة.
تتيح لك تحرير النقاط تعديل حدود الشكل أو استخدام شكل موجود كأساس لشكل جديد.
نصائح تحرير الشكل

عند تحرير أشكال PowerPoint، ضع في اعتبارك هذه المفاهيم:
- يمكن أن يكون الشكل (أو مساره) مغلقًا أو مفتوحًا.
- يتكون مسار الشكل من نقاط ربط متصلة بقطعات.
- يمكن أن تكون القطعة مستقيمة أو منحنية. تحدد نقاط الربط ومقابض التحكم شكلها.
- توجد نقاط الربط ك نقاط زاوية، نقاط مستقيمة، أو نقاط ناعمة:
- في نقطة الزاوية، يمكن أن تتقاطع القطع المتجاورة بزاوية.
- في نقطة ناعمة، يتم محاذاة مقبضين التحكم ويكونان على مسافة متساوية من نقطة الربط.
- في نقطة مستقيمة، يتم محاذاة مقبضين التحكم لكن قد تكون المسافات مختلفة عن نقطة الربط.
- تحريك نقاط الربط أو تعديل مقابضها يغيّر حدود الشكل.
لتحرير أشكال PowerPoint عبر نقاط التحرير، توفر Aspose.Slides الفئة GeometryPath.
- تمثل مثيلة GeometryPath مسار هندسة كائن GeometryShape.
- لاسترداد GeometryPath من مثيلة GeometryShape، يمكنك استخدام طريقة GeometryShape.getGeometryPaths.
- لتعيين GeometryPath لشكل، يمكنك استخدام هذه الطرق: GeometryShape.setGeometryPath للأشكال ذات المسار الفردي وGeometryShape.setGeometryPaths للأشكال ذات مسارات متعددة.
- لإضافة قطعات، يمكنك استخدام الطرق الموجودة تحت GeometryPath.
- باستخدام طريقتي GeometryPath.setStroke وGeometryPath.setFillMode، يمكنك تحديد مظهر مسار الهندسة.
- باستخدام طريقة GeometryPath.getPathData، يمكنك استرداد مسار الهندسة لكائن GeometryShape كمصفوفة من قطعات المسار.
- للوصول إلى خيارات تخصيص هندسة إضافية، يمكنك قراءة كائن java.awt.Shape عبر PathIterator وإعادة تشغيل القطعات على GeometryPath.
عمليات تحرير بسيطة
استخدم الطرق التالية لتحرير مسار هندسي. تُدخل النسخ التي تقبل فهرسًا قطعة في ذلك الموضع الصفري الأساس في مصفوفة قطعات المسار.
| العملية | الطريقة |
|---|---|
| إلحاق أو إدراج خط مستقيم. | GeometryPath.lineTo |
| إلحاق أو إدراج منحنى بيزير مكعب مع نقطتي تحكم ونقطة نهائية. | GeometryPath.cubicBezierTo |
| إلحاق أو إدراج منحنى بيزير رباعي مع نقطة تحكم واحدة ونقطة نهائية. | GeometryPath.quadraticBezierTo |
| إلحاق قوس بيضاوي بالأبعاد وزوايا محددة بالدرجات. | GeometryPath.arcTo |
| إغلاق الشكل الحالي بربط نقطته النهائية بنقطة البداية. | GeometryPath.closeFigure |
| تعيين نقطة البداية لشكل. | GeometryPath.moveTo |
| إزالة قطعة في فهرس صفري الأساس. | GeometryPath.removeAt |
إضافة نقاط مخصصة إلى شكل
هذا المثال يدخل نقطتين إلى شكل مستطيل باستخدام GeometryPath.lineTo ويطبق المسار المعدل عبر GeometryShape.setGeometryPath. الإحداثيات محلية إلى الشكل: كلا النقطتين الجديدتين في وسطه، على بعد 100 نقطة من اليسار و50 نقطة من الأعلى. يضع فهرسا القطعة 1 و4 النقطتين على الحافتين العلوية والسفلية، ساحبًا هاتين الحافتين إلى الداخل.
import jpype
import asposeslides
if not jpype.isJVMStarted():
jpype.startJVM()
from asposeslides.api import Presentation, ShapeType
presentation = Presentation()
try:
slide = presentation.getSlides().get_Item(0)
shape = slide.getShapes().addAutoShape(ShapeType.Rectangle, 100, 100, 200, 100)
geometry_path = shape.getGeometryPaths()[0]
geometry_path.lineTo(100, 50, 1)
geometry_path.lineTo(100, 50, 4)
shape.setGeometryPath(geometry_path)
finally:
presentation.dispose()

إزالة نقاط من شكل
هذا المثال يغيّر مخطط القلب بإزالة القطعة في الفهرس الصفري الأساس 2 عبر GeometryPath.removeAt. تطبيق المسار المعدل عبر GeometryShape.setGeometryPath يحدث حدود الشكل.
import jpype
import asposeslides
if not jpype.isJVMStarted():
jpype.startJVM()
from asposeslides.api import Presentation, ShapeType
presentation = Presentation()
try:
slide = presentation.getSlides().get_Item(0)
shape = slide.getShapes().addAutoShape(ShapeType.Heart, 100, 100, 300, 300)
geometry_path = shape.getGeometryPaths()[0]
geometry_path.removeAt(2)
shape.setGeometryPath(geometry_path)
finally:
presentation.dispose()

إنشاء شكل مخصص
هذا المثال يبني نجمة خماسية القمم باستخدام GeometryPath. تُعرّف نصف القطر الخارجي والداخلي المتناوب بـ 100 و50 نقطة لتحديد القمم والفراغات؛ خطوة 72 درجة توازن الخمس قمم بالتساوي. يُغلق المسار عبر GeometryPath.closeFigure ويُطبق على شكل مستطيل عبر GeometryShape.setGeometryPath، مستبدلًا مخططه.
import jpype
import asposeslides
if not jpype.isJVMStarted():
jpype.startJVM()
from asposeslides.api import Presentation, ShapeType, GeometryPath
import math
points = []
outer_radius = 100
inner_radius = 50
angle_step = 72
for angle in range(-90, 270, angle_step):
radians = math.radians(angle)
x = outer_radius * math.cos(radians)
y = outer_radius * math.sin(radians)
points.append((x + outer_radius, y + outer_radius))
radians = math.radians(angle + angle_step / 2)
x = inner_radius * math.cos(radians)
y = inner_radius * math.sin(radians)
points.append((x + outer_radius, y + outer_radius))
star_path = GeometryPath()
star_path.moveTo(*points[0])
for point in points[1:]:
star_path.lineTo(*point)
star_path.closeFigure()
presentation = Presentation()
try:
slide = presentation.getSlides().get_Item(0)
shape = slide.getShapes().addAutoShape(ShapeType.Rectangle, 100, 100, outer_radius * 2, outer_radius * 2)
shape.setGeometryPath(star_path)
finally:
presentation.dispose()

إنشاء شكل مركب مخصص
هذا المثال يعرّف مسارين مغلقين من نوع GeometryPath للثلثين العلوي والسفلي من الشكل. تطبيق كلا المسارين عبر GeometryShape.setGeometryPaths يُنشئ شكلًا مركبًا واحدًا به فجوة عبر الثلث الأوسط.
import jpype
import asposeslides
if not jpype.isJVMStarted():
jpype.startJVM()
from asposeslides.api import Presentation, ShapeType, GeometryPath
presentation = Presentation()
try:
slide = presentation.getSlides().get_Item(0)
shape = slide.getShapes().addAutoShape(ShapeType.Rectangle, 100, 100, 200, 100)
top_path = GeometryPath()
top_path.moveTo(0, 0)
top_path.lineTo(shape.getWidth(), 0)
top_path.lineTo(shape.getWidth(), shape.getHeight() / 3)
top_path.lineTo(0, shape.getHeight() / 3)
top_path.closeFigure()
bottom_path = GeometryPath()
bottom_path.moveTo(0, shape.getHeight() / 3 * 2)
bottom_path.lineTo(shape.getWidth(), shape.getHeight() / 3 * 2)
bottom_path.lineTo(shape.getWidth(), shape.getHeight())
bottom_path.lineTo(0, shape.getHeight())
bottom_path.closeFigure()
shape.setGeometryPaths([top_path, bottom_path])
finally:
presentation.dispose()

إنشاء شكل مخصص بزوايا منحنية
هذا المثال يجمع قطعات مستقيمة مع GeometryPath.arcTo لإنشاء شكل بزوايا منحنية إلى الداخل. يحدد كل حجم زاوية عرض وارتفاع القوس بالنقاط، ويرسم قطاعًا بزاوية -90 درجة ربع إهليلجي عكس اتجاه عقارب الساعة. يُطبق المسار المكتمل عبر GeometryShape.setGeometryPath ويُحفظ العرض بصيغة PPTX.
import jpype
import asposeslides
if not jpype.isJVMStarted():
jpype.startJVM()
from asposeslides.api import Presentation, ShapeType, GeometryPath, SaveFormat
shape_x = 20
shape_y = 20
shape_width = 300
shape_height = 200
left_top_size = 50
right_top_size = 20
right_bottom_size = 40
left_bottom_size = 10
presentation = Presentation()
try:
slide = presentation.getSlides().get_Item(0)
shape = slide.getShapes().addAutoShape(ShapeType.Custom, shape_x, shape_y, shape_width, shape_height)
geometry_path = GeometryPath()
geometry_path.moveTo(left_top_size, 0)
geometry_path.lineTo(shape_width - right_top_size, 0)
geometry_path.arcTo(right_top_size, right_top_size, 180, -90)
geometry_path.lineTo(shape_width, shape_height - right_bottom_size)
geometry_path.arcTo(right_bottom_size, right_bottom_size, -90, -90)
geometry_path.lineTo(left_bottom_size, shape_height)
geometry_path.arcTo(left_bottom_size, left_bottom_size, 0, -90)
geometry_path.lineTo(0, left_top_size)
geometry_path.arcTo(left_top_size, left_top_size, 90, -90)
geometry_path.closeFigure()
shape.setGeometryPath(geometry_path)
presentation.save("output.pptx", SaveFormat.Pptx)
finally:
presentation.dispose()
معرفة ما إذا كانت هندسة الشكل مغلقة
يتحقق المساعد التالي ما إذا كان كل مسار هندسي غير فارغ ينتهي بقطعة إغلاق. يقرأ GeometryPath.getPathData ويقارن أمر القطعة النهائي PathSegment.getPathCommand مع PathCommandType.Close. يرجّع False عندما لا توجد مسارات غير فارغة. يتحقق هذا من الأمر النهائي لكل مسار؛ لا يفحص كل شكل فرعي داخل المسار. يطبع المثال True لمستطيل.
import jpype
import asposeslides
if not jpype.isJVMStarted():
jpype.startJVM()
from asposeslides.api import Presentation, ShapeType, PathCommandType
def is_geometry_closed(geometry_shape):
has_nonempty_path = False
for geometry_path in geometry_shape.getGeometryPaths():
path_data = geometry_path.getPathData()
if len(path_data) == 0:
continue
last_segment = path_data[-1]
has_nonempty_path = True
if last_segment.getPathCommand() != PathCommandType.Close:
return False
return has_nonempty_path
presentation = Presentation()
try:
slide = presentation.getSlides().get_Item(0)
shape = slide.getShapes().addAutoShape(ShapeType.Rectangle, 100, 100, 200, 100)
closed = is_geometry_closed(shape)
print(closed)
finally:
presentation.dispose()
استخراج وفحص عناصر الشكل
استخدم GeometryShape.createShapeElements لفحص شكل موجود كعناصر تحتوي على نقاط المسار وأنواع النقاط. حوّل كل عنصر عائد إلى ShapeElement عبر jpype.JObject لاسترجاع مصفوفات النقاط والأنواع عبر طريقتي getPathPoints وgetPathTypes. يُعد هذا التمثيل مفيدًا لتحليل الحدود أو تمرير الهندسة إلى أدوات رسم مخصصة.
يفتح المثال input.pptx، يجد أول شكل هندسي مباشرة على الشريحة الأولى، ويطبع قطعات مساره. استخدم عرضًا يحتوي على شكل بقطعات مستقيمة ومنحنية لرؤية كلا النوعين من الإخراج. لا يبحث داخل المجموعات.
import jpype
import asposeslides
if not jpype.isJVMStarted():
jpype.startJVM()
from asposeslides.api import Presentation, GeometryShape, ShapeElement
presentation = Presentation("input.pptx")
try:
slide = presentation.getSlides().get_Item(0)
shape = None
for candidate in slide.getShapes():
if isinstance(candidate, GeometryShape):
shape = candidate
break
if shape is None:
print("No geometry shape found on the first slide.")
else:
for element in shape.createShapeElements():
shape_element = jpype.JObject(element, ShapeElement)
points = shape_element.getPathPoints()
types = shape_element.getPathTypes()
print("Shape element:")
i = 0
while i < len(points):
point_type = types[i] & 0x07 # احتفظ بنوع القطعة، مع استبعاد الأعلام.
if point_type == 0:
print(f"Start figure at {points[i]}")
elif point_type == 1:
print(f"Line to {points[i]}")
elif point_type == 3:
print(f"Cubic Bezier: controls {points[i]}, {points[i + 1]}; end {points[i + 2]}")
i += 2 # Consume both control points and the endpoint.
if types[i] & 0x80:
print("Close subpath back to its starting point.")
i += 1
finally:
presentation.dispose()
المصفوفات متطابقة بحسب الفهرس: points[i] هي نقطة وtypes[i] تصف دورها. النوع 0 يبدأ شكلًا، والنوع 1 يرسم خطًا من النقطة السابقة إلى الحالية. يبدأ القطعة المكعبة بيزير من نقطة النهاية السابقة وتستخدم ثلاث مداخل متتالية من النوع 3: نقطتي تحكم ثم نقطة النهاية الجديدة. يُستهلك هذا الثلاثة معًا في الحلقة ويتحقق من نقطة النهاية للإغلاق.
تحتوي أنواع النقاط أيضًا على أعلام. قُم بعملية قناع بـ 0x07 لتحديد نوع القطعة، واختبر 0x80 منفصلًا لاكتشاف مسار فرعي مغلق. على سبيل المثال، 129 (0x81) يعني نقطة نهاية خط تُغلق مسارًا فرعيًا، و131 (0x83) يعني نقطة نهاية بيزير تُغلق واحدًا. تخزن Java هذه الأعلام في بايتات موقّعة؛ القناع يحافظ على القيم البتية المقصودة. مقارنة القيمة الخام فقط بـ 1 أو 3 سيفوت هذه الحالات. الأعلام الأخرى، مثل الشرط (0x10) والعلامة (0x20)، لا تغيّر نوع القطعة الأساسي.
الإحداثيات والتحويلات. تُقاس النقاط بوحدات النقطة (1/72 بوصة)، مع زيادة X نحو اليمين وزيادة Y نحو الأسفل. بالنسبة لشكل غير مجمع، تتضمن الإحداثيات المستخرجة بالفعل الموضع المسترجع عبر getX وgetY وتعكس عرضه وارتفاعه الحالي. على سبيل المثال، مستطيل غير مُدوَّر عند (100, 200) بحجم 120 × 80 له حدود حدودية (100, 200)–(220, 280). لا تُضيف موضع الشكل مرة أخرى. اطرح X وY للحصول على إحداثيات نسبةً إلى إطاره غير المدور.
النقاط المستخرجة لا تُطبق دوران الشكل المسترجع عبر getRotation، ولا الانعكاسات الأفقية والرأسية. لإعادة إنتاج الحدود المعروضة، طبّق تلك التحويلات حول مركز الشكل. بالنسبة للأشكال المجمّعة، احسب أيضًا خريطة إحداثيات المجموعة وتحويلاتها؛ لا تفترض أن النقاط المستخرجة هي إحداثيات الشريحة النهائية أو تُطبق ترجمة أو مقياسًا مرتين إذا كان بالفعل مُعكسًا في إطار الطفل.
الفحص مقابل التحرير. تصف المصفوفات المستخرجة الحدود؛ تعديلها لا يُحدّث الشكل الأصلي. اختر هذا النهج عندما تحتاج إلى مصفوفات النقاط/الأنواع في تحليل أو كود رسم. استخدم getGeometryPaths عندما تحتاج إلى أوامر هندسية في نظام إحداثيات الشكل المحلي أو لتعديل هندسته. طبّق المسارات المعدلة عبر setGeometryPath أو setGeometryPaths.
تحويل شكل Java AWT إلى GeometryPath
يجمع هذا المثال مخططًا مستطيلًا مع نص محوَّل إلى هندسة. تُعطّل طريقة setFillMode الملء للمخطط. يُنشئ GlyphVector مخطط النص في كائن java.awt.Shape. يزوِّد PathIterator بحركات، خطوط، قطعات رباعية، مكعبية، وإغلاق، والتي يعيد المثال تشغيلها على GeometryPath.
يستخدم النص خطًا Arial مقاسه 40 نقطة. يُزاح المخطط 20 نقطة أفقيًا ويُوضَع بحيث يكون أعلىه البصري 10 نقاط أسفل حافة أعلى الشكل. يُملأ مسار النص، بينما يبقى المستطيل الأصلي مخططًا فقط. يطبق كلا المسارين عبر GeometryShape.setGeometryPaths لتكوين شكل واحد يحتوي على المستطيل والنص.
import jpype
import asposeslides
if not jpype.isJVMStarted():
jpype.startJVM()
from asposeslides.api import Presentation, ShapeType, GeometryPath, PathFillModeType
from java.awt import Font
from java.awt.geom import PathIterator
from java.awt.image import BufferedImage
presentation = Presentation()
try:
slide = presentation.getSlides().get_Item(0)
shape = slide.getShapes().addAutoShape(ShapeType.Rectangle, 100, 100, 300, 100)
# الحصول على مسار الهندسة للشكل.
original_path = shape.getGeometryPaths()[0]
original_path.setFillMode(PathFillModeType.None_)
# إنشاء مسار رسومي جديد بالنص.
font = Font("Arial", Font.PLAIN, 40)
text = "Text in shape"
image = BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB)
graphics = image.createGraphics()
try:
font_context = graphics.getFontRenderContext()
glyph_vector = font.createGlyphVector(font_context, text)
text_bounds = glyph_vector.getVisualBounds()
baseline = -text_bounds.getY() + 10
graphics_path = glyph_vector.getOutline(20.0, baseline)
finally:
graphics.dispose()
# تحويل المسار الرسومي إلى مسار هندسي.
text_path = GeometryPath()
path_iterator = graphics_path.getPathIterator(None)
points = jpype.JArray(jpype.JFloat)(6)
while not path_iterator.isDone():
segment_type = path_iterator.currentSegment(points)
if segment_type == PathIterator.SEG_MOVETO:
text_path.moveTo(points[0], points[1])
elif segment_type == PathIterator.SEG_LINETO:
text_path.lineTo(points[0], points[1])
elif segment_type == PathIterator.SEG_QUADTO:
text_path.quadraticBezierTo(points[0], points[1], points[2], points[3])
elif segment_type == PathIterator.SEG_CUBICTO:
text_path.cubicBezierTo(points[0], points[1], points[2], points[3], points[4], points[5])
elif segment_type == PathIterator.SEG_CLOSE:
text_path.closeFigure()
path_iterator.next()
text_path.setFillMode(PathFillModeType.Normal)
# تطبيق مسار النص مع مسار الهندسة الأصلي.
shape.setGeometryPaths([original_path, text_path])
finally:
presentation.dispose()

الأسئلة المتكررة
ماذا سيحدث للملء والحد عند استبدال الهندسة؟
يبقى النمط مرتبطًا بالشكل؛ فقط يتغيّر المخطط. يتم تطبيق الملء والحد تلقائيًا على الهندسة الجديدة.
كيف يمكنني تدوير شكل مخصص مع هندسته بشكل صحيح؟
استخدم طريقة الشكل setRotation؛ فإن الهندسة تدور مع الشكل لأنها مرتبطة بنظام إحداثيات الشكل نفسه.
هل يمكنني تحويل الشكل المخصص إلى صورة لتثبيت النتيجة؟
نعم. صدّر منطقة slide المطلوبة أو shape نفسها إلى تنسيق نقطي؛ يبسط ذلك العمل اللاحق مع الهندسات المعقّدة.